diff --git a/CREDITS.md b/CREDITS.md index a5c854fdc..c996e63cb 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -7,6 +7,7 @@ This page lists all the individual contributions to the project by their author. - **Belonit (Gluk-v48)**: - Check for Changelog/Documentation/Credits in Pull Requests. - Docs dark theme switcher. + - Porting the YR MP spawner from C to C++ and YR++, used as a base for the Vinifera spawner. - **CCHyper/tomsons26**: - Vinifera foundations: TS++, game.exe hooker, extension system and other core features - Implement `CurleyShuffle` for AircraftTypes @@ -130,6 +131,8 @@ This page lists all the individual contributions to the project by their author. - Implement various controls to customise target lasers line. - Implement various controls to show and customise NavCom queue lines. - Implement customizable mouse cursors and actions. +- **CnCNet Contributors**: + - Tiberian Sun TS-patches spawner, Yuri's Revenge CnCNet spawner that served as a base for Vinifera spawner. - **Kerbiter (Metadorius)**: - Initial documentation setup. - **MarkJFox**: @@ -200,4 +203,5 @@ This page lists all the individual contributions to the project by their author. - Update and finalize custom mouse cursors and actions, add customizable weapon & EMP cursors. - Implement support for a Saved Games subdirectory. - Fix a bug where if the player loaded a saved game, the score screen timer would report the time since the saved game was loaded, instead of since when the scenario was first started. + - Implement the multiplayer spawner. diff --git a/docs/Miscellaneous.md b/docs/Miscellaneous.md index e9bf623be..f69bd911e 100644 --- a/docs/Miscellaneous.md +++ b/docs/Miscellaneous.md @@ -12,6 +12,117 @@ This page describes every change in Vinifera that wasn't categorized into a prop - It is no longer required to list all Tiberiums in a map to override some Tiberium's properties. - It was possible to make a building get built at a different speed than its cost implied by giving the building a `FreeUnit` or `PadAircraft` that was more expensive than the building itself. This functionality has been removed in preference of `BuildTimeCost`. +## Spawner + +- Vinifera implements its own spawner, capable of starting a new singleplayer, skirmish or multiplayer game, as well as loading saved games. +- To start the game in spawner mode, the `-SPAWN` command line argument must be specified. +- The spawner's options can be configures in `SPAWN.INI`. + +In `SPAWN.INI`: +```ini +[Settings] +; Game Mode Options +Bases=yes ; boolean, do players start with MCVs/Construction Yards? +Credits=10000 ; integer, starting amount of credits for the players. +BridgeDestroy=yes ; boolean, can bridges be destroyed? +Crates=no ; boolean, are crates enabled? +ShortGame=no ; boolean, is short game enabled? +BuildOffAlly=no ; boolean, is building off ally bases allowed? +GameSpeed=0 ; integer, starting game speed. +MultiEngineer=no ; boolean, is multi-engineer enabled? +UnitCount=0 ; integer, starting unit count. +AIPlayers=0 ; integer, number of AI players. +AIDifficulty=1 ; integer, AI difficulty. +AlliesAllowed=no ; boolean, can players form and break alliances in-game? +HarvesterTruce=no ; boolean, are harvesters invulnerable? +FogOfWar=no ; boolean, is fog of war enabled? +MCVRedeploy=yes ; boolean, can MCVs be redeployed? + +; Savegame Options +LoadSaveGame=no ; boolean, should the spawner load a saved game, as opposed to starting a new scenario? +SavedGamesDir=Saved Games ; string, name (path) of the subfolder containing saved games. Supports nesting, e. g. Saved Games\Tiberian Sun. +SaveGameName= ; string, name of the saved game to load. + +; Scenario Options +Seed=0 ; integer, random seed. +TechLevel=10 ; integer, maximum tech level. +IsCampaign=no ; boolean, is the game that is about to start campaign, as opposed to skirmish? +CampaignID=-1 ; integer, ID of the campaign (from BATTLE.INI) to start +Tournament=0 ; integer, WOL Tournament Type +WOLGameID=3735928559 ; unsigned integer, WOL Game ID +ScenarioName=spawnmap.ini ; string, name of the scenario (map) to load. +MapHash= ; string, map hash, only used in statistics collection. +UIMapName= ; string, name of the map, only used in statistics collection. + +; Network Options +Protocol=2 ; integer, network protocol to use. +FrameSendRate=4 ; integer, starting FrameSendRate value. +ReconnectTimeout=2400 ; integer, player reconnection timeout. +ConnTimeout=3600 ; integer, player connection timeout. +MaxAhead=-1 ; integer, starting MaxHead value. +PreCalcMaxAhead=0 ; integer, starting PrecalcMaxHead value. +MaxLatencyLevel=255 ; unsigned byte, maximum allowed Protocol 0 latency level. + +; Tunnel Options +TunnelId=0 ; integer, tunnel ID. +TunnelIp=0.0.0.0 ; string, tunnel IP. +TunnelPort=0 ; integer, tunnel port. +ListenPort=1234 ; integer, listen port. + +; Extra Options +Firestorm=yes ; boolean, should the game start with Firestorm enabled? +QuickMatch=no ; boolean, should the game start in Quick Match mode? +SkipScoreScreen=no ; boolean, should the score screen be skipped once the game is over? +WriteStatistics=no ; boolean, should statistics be sent? +AINamesByDifficulty=no ; boolean, should AI players have their difficulty in their name? +CoachMode=no ; boolean, should defeated players that have allies not have the entire map revealed to them upon death? +AutoSurrender=yes ; boolean, should players surrender on disconnection, as opposed to turning their base over to the AI? +``` + +- Information about the local player is read from the `Settings` section, for all other players - from `OtherX` sections, where `X` ranges from `1` to `7`. + +In `SPAWN.INI`: +```ini +[PLAYERSECTION] +IsHuman=no ; boolean, is this a human player? +Name= ; string, the player's name. +Color=-1 ; integer, the player's color. +House=-1 ; integer, the player's house. +Difficulty=-1 ; integer, the player's difficulty. +Ip=0.0.0.0 ; string, the player's IP address. +Port=-1 ; integer, the player's port. +``` + +- Additionally, AI players (always come after human players) have these options parsed from sections of the format `MultiX`, where `X` ranges from `1` to `8`. + +In `SPAWN.INI`: +```ini +[MULTISECTION] +Color=-1 ; integer, the player's color. +House=-1 ; integer, the player's house. +Difficulty=-1 ; integer, the player's difficulty. +``` + +- Additionally, the spawner reads configuration for each house. Player houses come first, in the order of their color (increasing), then AI houses. +- Alliances are read from sections of the format `MultiX_Alliances`, where `X` ranges from `1` to `8`. + +In `SPAWN.INI`: +```ini +[MULTISECTION] +IsSpectator=no ; boolean, is this house a spectator (observer)? +SpawnLocations=-2 ; integer, spawn location of this house. 90 and -1 mean spectator, -2 means random. + +[ALLIANCESSECTION] +HouseAllyOne=-1 ; integer, index of the house this house is allied to, -1 means none. +HouseAllyTwo=-1 ; integer, index of the house this house is allied to, -1 means none. +HouseAllyThree=-1 ; integer, index of the house this house is allied to, -1 means none. +HouseAllyFour=-1 ; integer, index of the house this house is allied to, -1 means none. +HouseAllyFive=-1 ; integer, index of the house this house is allied to, -1 means none. +HouseAllySix=-1 ; integer, index of the house this house is allied to, -1 means none. +HouseAllySeven=-1 ; integer, index of the house this house is allied to, -1 means none. +HouseAllyEight=-1 ; integer, index of the house this house is allied to, -1 means none. +``` + ## Quality of Life - Harvesters are now considered when executing the "Guard" command. They have a special case when assigned with the Guard mission that tells them to find the nearest Tiberium patch and begin harvesting. @@ -156,7 +267,10 @@ Due to the nature of its use, this feature is only available when Vinifera is ru ### Command Line Options -- Vinifera adds a number of command-line arguments allowing the user to skip the startup movies, or skip directly to a specific game mode and/or dialog. +- Vinifera adds a number of command-line arguments. + +- `-SPAWN` +Launch the game in spawner mode. - `-NO_STARTUP_VIDEO` Skips all startup movies. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 782fc0b1e..4c7c58a04 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -163,6 +163,7 @@ New: - Implement customizable mouse cursors and actions (by CCHyper/tomsons26, ZivDero) - Implement support for a Saved Games subdirectory (by ZivDero) +- Implemented the multiplayer spawner (by ZivDero) Vanilla fixes: - Fix HouseType `Nod` having the `Prefix=B` and `Side=GDI` in vanilla `rules.ini` by setting them to `N` and `Nod`, respectively (by CCHyper/tomsons26) diff --git a/src/cncnet/cncnet4/cncnet4.cpp b/src/cncnet/cncnet4/cncnet4.cpp deleted file mode 100644 index 9c6e57842..000000000 --- a/src/cncnet/cncnet4/cncnet4.cpp +++ /dev/null @@ -1,413 +0,0 @@ -/******************************************************************************* -/* O P E N S O U R C E -- V I N I F E R A ** -/******************************************************************************* - * - * @project Vinifera - * - * @file CNCNET4.CPP - * - * @author CCHyper (Based on work by Toni Spets) - * - * @brief CnCNet4 replacements for low level networking API. - * - * @license Vinifera 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 - * 3 of the License, or (at your option) any later version. - * - * Vinifera 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 this program. - * If not, see . - * - ******************************************************************************/ -#include "cncnet4.h" -#include "cncnet4_net.h" -#include "cncnet4_globals.h" -#include "rawfile.h" -#include "ini.h" -#include "debughandler.h" -#include "asserthandler.h" - -#include -#include -#include -#include -#include - - -/** - * Initialises the CnCNet4 system. - */ -bool __stdcall CnCNet4::Init() -{ - RawFileClass file("SUN.INI"); - INIClass ini; - - /** - * Load the CnCNet4 settings. - */ - ini.Load(file); - if (ini.Is_Present("CnCNet4")) { - CnCNet4::IsEnabled = ini.Get_Bool("CnCNet4", "Enabled", CnCNet4::IsEnabled); - ini.Get_String("CnCNet4", "Host", CnCNet4::Host, sizeof(CnCNet4::Host)); - CnCNet4::Peer2Peer = ini.Get_Bool("CnCNet4", "P2P", CnCNet4::Peer2Peer); - CnCNet4::UseUDP = ini.Get_Bool("CnCNet4", "UDP", CnCNet4::UseUDP); - CnCNet4::Port = ini.Get_Int("CnCNet4", "Port", CnCNet4::Port); - } - - if (!CnCNet4::IsEnabled) { - - /** - * Nothing went wrong, but the CnCNet4 interface is not enabled. - */ - return true; - } - - /** - * Check to make sure the CnCNet4 DLL is not present in the directory. - */ - if (RawFileClass("wsock32.dll").Is_Available()) { - MessageBox(nullptr, "File conflict detected!\nPlease remove WSOCK32.DLL from the game directory!", "Vinifera", MB_OK|MB_ICONEXCLAMATION); - return false; - } - if (RawFileClass("cncnet.dll").Is_Available()) { - MessageBox(nullptr, "File conflict detected!\nPlease remove CNCNET.DLL from the game directory!", "Vinifera", MB_OK|MB_ICONEXCLAMATION); - return false; - } - - int s = net_init(); - net_opt_reuse(); - - DEBUG_INFO("CnCNet4: Initialising...\n"); - - if (CnCNet4::Port < 1024 || CnCNet4::Port > 65534) { - CnCNet4::Port = 9001; - } - - DEBUG_INFO("CnCNet4: Broadcasting to \"%s:%d\".\n", CnCNet4::Host, CnCNet4::Port); - - CnCNet4::IsDedicated = true; - - if (!net_address(&CnCNet4::Server, CnCNet4::Host, CnCNet4::Port)) { - return false; - } - - if (CnCNet4::Peer2Peer) { - - /** - * Test P2P. - */ - bool pass = false; - - fd_set rfds; - struct timeval tv; - struct sockaddr_in to; - struct sockaddr_in from; - int start = time(nullptr); - - net_write_int8(CMD_TESTP2P); - net_write_int32(start); - - while (time(nullptr) < start + 5) { - - net_send_noflush(&to); - - FD_ZERO(&rfds); - FD_SET(s, &rfds); - tv.tv_sec = 1; - tv.tv_usec = 0; - - if (select(s + 1, &rfds, NULL, NULL, &tv) > -1) { - if (FD_ISSET(s, &rfds)) { - net_recv(&from); - - if (net_read_int8() == CMD_TESTP2P && net_read_int32() == start) { - pass = true; - break; - } - } - } - } - - /** - * Enable P2P if passed. - */ - if (pass) { - DEBUG_INFO("CnCNet4: Peer-to-peer test passed.\n"); - DEBUG_INFO("CnCNet4: Peer-to-peer is enabled.\n"); - net_bind("0.0.0.0", 8054); - - } else { - DEBUG_WARNING("CnCNet4: Peer-to-peer test failed!\n"); - //return false; - } - - } - - return true; -} - - - -/** - * Shutdown the CnCNet4 system. - */ -void __stdcall CnCNet4::Shutdown() -{ - net_free(); -} - - -SOCKET __stdcall CnCNet4::socket(int af, int type, int protocol) -{ -#ifndef NDEBUG - DEV_DEBUG_INFO("CnCNet4: socket(af=%08X, type=%08X, protocol=%08X)\n", af, type, protocol); -#endif - - if (af == AF_IPX) { - return net_socket; - } - - return ::socket(af, type, protocol); -} - - -int __stdcall CnCNet4::bind(SOCKET s, const struct sockaddr *name, int namelen) -{ -#ifndef NDEBUG - DEV_DEBUG_INFO("CnCNet4: bind(s=%d, name=%p, namelen=%d)\n", s, name, namelen); -#endif - - if (s == net_socket) { - return 0; - } - - return ::bind(s, name, namelen); -} - - -int __stdcall CnCNet4::recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen) -{ -#ifndef NDEBUG - //DEV_DEBUG_INFO("CnCNet4: recvfrom(s=%d, buf=%p, len=%d, flags=%08X, from=%p, fromlen=%p (%d))\n", s, buf, len, flags, from, fromlen, *fromlen); -#endif - - if (s == net_socket) { - int ret; - struct sockaddr_in from_in; - - ret = net_recv(&from_in); - - if (ret > 0) { - - if (CnCNet4::IsDedicated) { - - if (from_in.sin_addr.s_addr == CnCNet4::Server.sin_addr.s_addr && from_in.sin_port == CnCNet4::Server.sin_port) { - uint8_t cmd = net_read_int8(); - - /** - * Handle keepalive packets from server, very special case. - */ - if (cmd == CMD_PING) { - net_write_int8(CMD_PING); - net_write_int32(net_read_int32()); - net_send(&from_in); - - /** - * #FIXME: returning 0 means disconnected - */ - return 0; - } - - /** - * P2p flag. - */ - from_in.sin_zero[0] = cmd; - - from_in.sin_addr.s_addr = net_read_int32(); - from_in.sin_port = net_read_int16(); - - } else if (CnCNet4::Peer2Peer) { - /** - * P2p flag for direct packets. - */ - from_in.sin_zero[0] = 1; - - } else { - /** - * Discard p2p packets if not in p2p mode. - * #FIXME: returning 0 means disconnected - */ - return 0; - } - - /** - * Force p2p port. - */ - if (from_in.sin_zero[0]) { - from_in.sin_port = htons(8054); - } - - in2ipx(&from_in, (struct sockaddr_ipx *)from); - - } else { - in2ipx(&from_in, (struct sockaddr_ipx *)from); - } - - ret = net_read_data((void *)buf, len); - } - - return ret; - } - - return ::recvfrom(s, buf, len, flags, from, fromlen); -} - - -int __stdcall CnCNet4::sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen) -{ -#ifndef NDEBUG - //DEV_DEBUG_INFO("CnCNet4: sendto(s=%d, buf=%p, len=%d, flags=%08X, to=%p, tolen=%d)\n", s, buf, len, flags, to, tolen); -#endif - - if (to->sa_family == AF_IPX) { - struct sockaddr_in to_in; - - if (CnCNet4::IsDedicated) { - - if (is_ipx_broadcast((struct sockaddr_ipx *)to)) { - net_write_int8(CnCNet4::Peer2Peer ? 1 : 0); - net_write_int32(0xFFFFFFFF); - net_write_int16(0xFFFF); - net_write_data((void *)buf, len); - net_send(&CnCNet4::Server); - - } else { - - ipx2in((struct sockaddr_ipx *)to, &to_in); - - /** - * Use p2p only if both clients are in p2p mode. - */ - if (to_in.sin_zero[0] && CnCNet4::Peer2Peer) { - net_write_data((void *)buf, len); - net_send(&to_in); - - } else { - net_write_int8(CnCNet4::Peer2Peer ? 1 : 0); - net_write_int32(to_in.sin_addr.s_addr); - net_write_int16(to_in.sin_port); - net_write_data((void *)buf, len); - net_send(&CnCNet4::Server); - } - } - - return len; - } - - ipx2in((struct sockaddr_ipx *)to, &to_in); - net_write_data((void *)buf, len); - - /** - * Check if it's a broadcast. - */ - if (is_ipx_broadcast((struct sockaddr_ipx *)to)) { - net_send(&CnCNet4::Server); - return len; - - } else { - return net_send(&to_in); - } - } - - return ::sendto(s, buf, len, flags, to, tolen); -} - - -int __stdcall CnCNet4::getsockopt(SOCKET s, int level, int optname, char *optval, int *optlen) -{ -#ifndef NDEBUG - DEV_DEBUG_INFO("CnCNet4: getsockopt(s=%d, level=%08X, optname=%08X, optval=%p, optlen=%p (%d))\n", s, level, optname, optval, optlen, *optlen); -#endif - - if (level == 0x3E8) { - *optval = 1; - *optlen = 1; - return 0; - } - - if (level == 0xFFFF) { - *optval = 1; - *optlen = 1; - return 0; - } - - return ::getsockopt(s, level, optname, optval, optlen); -} - - -int __stdcall CnCNet4::setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen) -{ -#ifndef NDEBUG - DEV_DEBUG_INFO("CnCNet4: setsockopt(s=%d, level=%08X, optname=%08X, optval=%p, optlen=%d)\n", s, level, optname, optval, optlen); -#endif - - if (level == 0x3E8) { - return 0; - } - if (level == 0xFFFF) { - return 0; - } - - return ::setsockopt(s, level, optname, optval, optlen); -} - - -int __stdcall CnCNet4::closesocket(SOCKET s) -{ -#ifndef NDEBUG - DEV_DEBUG_INFO("CnCNet4: closesocket(s=%d)\n", s); -#endif - - if (s == net_socket) { - if (CnCNet4::IsDedicated) { - net_write_int8(CMD_DISCONNECT); - net_send(&CnCNet4::Server); - } - return 0; - } - - return ::closesocket(s); -} - - -int __stdcall CnCNet4::getsockname(SOCKET s, struct sockaddr *name, int *namelen) -{ -#ifndef NDEBUG - DEV_DEBUG_INFO("CnCNet4: getsockname(s=%d, name=%p, namelen=%p (%d)\n", s, name, namelen, *namelen); -#endif - - if (s == net_socket) { - struct sockaddr_in name_in; - char hostname[256]; - struct hostent *he; - - gethostname(hostname, 256); - he = ::gethostbyname(hostname); - - DEBUG_INFO("getsockname: local hostname: %s\n", hostname); - - if (he) { - DEBUG_INFO("getsockname: local ip: %s\n", inet_ntoa(*(struct in_addr *)(he->h_addr_list[0]))); - name_in.sin_addr = *(struct in_addr *)(he->h_addr_list[0]); - in2ipx(&name_in, (struct sockaddr_ipx *)name); - } - } - - return ::getsockname(s, name, namelen);; -} diff --git a/src/cncnet/cncnet4/cncnet4.h b/src/cncnet/cncnet4/cncnet4.h deleted file mode 100644 index a88d95fea..000000000 --- a/src/cncnet/cncnet4/cncnet4.h +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* -/* O P E N S O U R C E -- V I N I F E R A ** -/******************************************************************************* - * - * @project Vinifera - * - * @file CNCNET4.H - * - * @author CCHyper - * - * @brief CnCNet4 replacements for low level networking API. - * - * @license Vinifera 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 - * 3 of the License, or (at your option) any later version. - * - * Vinifera 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 this program. - * If not, see . - * - ******************************************************************************/ -#pragma once - -#include -#include - - -namespace CnCNet4 { - -bool __stdcall Init(); -void __stdcall Shutdown(); - -int __stdcall bind(SOCKET s, const struct sockaddr *name, int namelen); -SOCKET __stdcall socket(int af, int type, int protocol); -int __stdcall recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen); -int __stdcall sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen); -int __stdcall getsockopt(SOCKET s, int level, int optname, char *optval, int *optlen); -int __stdcall setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen); -int __stdcall closesocket(SOCKET s); -int __stdcall getsockname(SOCKET s, struct sockaddr *name, int *namelen); - -}; // namespace CnCNet4 diff --git a/src/cncnet/cncnet4/cncnet4_hooks.cpp b/src/cncnet/cncnet4/cncnet4_hooks.cpp deleted file mode 100644 index b0a8e488b..000000000 --- a/src/cncnet/cncnet4/cncnet4_hooks.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/******************************************************************************* -/* O P E N S O U R C E -- V I N I F E R A ** -/******************************************************************************* - * - * @project Vinifera - * - * @file CNCNET4_HOOKS.CPP - * - * @author CCHyper - * - * @brief Contains the hooks for the CnCNet4 system. - * - * @license Vinifera 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 - * 3 of the License, or (at your option) any later version. - * - * Vinifera 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 this program. - * If not, see . - * - ******************************************************************************/ -#include "cncnet4_hooks.h" -#include "cncnet4_globals.h" -#include "cncnet4.h" -#include "tibsun_globals.h" -#include "session.h" -#include "wspudp.h" -#include "wspipx.h" -#include "debughandler.h" - -#include "hooker.h" -#include "hooker_macros.h" - - -static int __stdcall bind_intercept(SOCKET s, const struct sockaddr *name, int namelen) -{ - if (CnCNet4::IsEnabled) { - return CnCNet4::bind(s, name, namelen); - } else { - return ::bind(s, name, namelen); - } -} - -static int __stdcall closesocket_intercept(SOCKET s) -{ - if (CnCNet4::IsEnabled) { - return CnCNet4::closesocket(s); - } else { - return ::closesocket(s); - } -} - -static int __stdcall getsockname_intercept(SOCKET s, struct sockaddr *name, int *namelen) -{ - if (CnCNet4::IsEnabled) { - return CnCNet4::getsockname(s, name, namelen); - } else { - return ::getsockname(s, name, namelen); - } -} - -static int __stdcall getsockopt_intercept(SOCKET s, int level, int optname, char *optval, int *optlen) -{ - if (CnCNet4::IsEnabled) { - return CnCNet4::getsockopt(s, level, optname, optval, optlen); - } else { - return ::getsockopt(s, level, optname, optval, optlen); - } -} - -static int __stdcall recvfrom_intercept(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen) -{ - if (CnCNet4::IsEnabled) { - return CnCNet4::recvfrom(s, buf, len, flags, from, fromlen); - } else { - return ::recvfrom(s, buf, len, flags, from, fromlen); - } -} - -static int __stdcall sendto_intercept(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen) -{ - if (CnCNet4::IsEnabled) { - return CnCNet4::sendto(s, buf, len, flags, to, tolen); - } else { - return ::sendto(s, buf, len, flags, to, tolen); - } -} - -static int __stdcall setsockopt_intercept(SOCKET s, int level, int optname, const char *optval, int optlen) -{ - if (CnCNet4::IsEnabled) { - return CnCNet4::setsockopt(s, level, optname, optval, optlen); - } else { - return ::setsockopt(s, level, optname, optval, optlen); - } -} - -static SOCKET __stdcall socket_intercept(int af, int type, int protocol) -{ - if (CnCNet4::IsEnabled) { - return CnCNet4::socket(af, type, protocol); - } else { - return ::socket(af, type, protocol); - } -} - - -/** - * #issue-504 - * - * Create the CnCNet4 UDP interface or standard UDP interface depending - * on if the CnCNet4 system has been enabled. - * - * @author: CCHyper - */ -static void CnCNet_Create_PacketTransport() -{ - bool created = false; - - if (CnCNet4::IsEnabled && CnCNet4::UseUDP) { - PacketTransport = new UDPInterfaceClass(); - if (PacketTransport) { - DEBUG_INFO("UDP PacketTransport for CnCNet4.\n"); - } - - created = (PacketTransport != nullptr); - } - - if (!created) { - PacketTransport = new IPXInterfaceClass(); - if (PacketTransport) { - DEBUG_INFO("IPX PacketTransport created.\n"); - } - } - - if (!PacketTransport) { - DEBUG_ERROR("Failed to create PacketTransport!\n"); - } -} - - -/** - * #issue-504 - * - * This patch replaces the call to the IPXInterfaceClass constructor when - * setting up the PacketTransport for network multiplayer games with - * conditional code that creates the UDPInterfaceClass when enabled. - * - * @author: CCHyper - */ -DECLARE_PATCH(_Select_Game_Network_Create_PacketTransport_Patch) -{ - Session.Type = GAME_IPX; - Session.CommProtocol = COMM_PROTOCOL_MULTI_E_COMP; - - if (!PacketTransport) { - CnCNet_Create_PacketTransport(); - } - - JMP(0x004E2698); -} - - -/** - * Main function for patching the hooks. - */ -void CnCNet4_Hooks() -{ - Patch_Jump(0x006B4D54, &bind_intercept); - Patch_Jump(0x006B4D4E, &closesocket_intercept); - //Patch_Jump(0x, &getsockname_intercept); - Patch_Jump(0x006B4D48, &getsockopt_intercept); - Patch_Jump(0x006B4D66, &recvfrom_intercept); - Patch_Jump(0x006B4D6C, &sendto_intercept); - Patch_Jump(0x006B4D60, &setsockopt_intercept); - Patch_Jump(0x006B4D5A, &socket_intercept); - - Patch_Jump(0x004E2656, &_Select_Game_Network_Create_PacketTransport_Patch); -} diff --git a/src/cncnet/cncnet4/cncnet4_net.cpp b/src/cncnet/cncnet4/cncnet4_net.cpp deleted file mode 100644 index c7b2f664e..000000000 --- a/src/cncnet/cncnet4/cncnet4_net.cpp +++ /dev/null @@ -1,310 +0,0 @@ -/******************************************************************************* -/* O P E N S O U R C E -- V I N I F E R A ** -/******************************************************************************* - * - * @project Vinifera - * - * @file CNCNET4_NET.CPP - * - * @author CCHyper (Based on work by Toni Spets) - * - * @brief Network utility functions. - * - * @license Vinifera 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 - * 3 of the License, or (at your option) any later version. - * - * Vinifera 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 this program. - * If not, see . - * - ******************************************************************************/ -#include -#include -#include - -#include "cncnet4_net.h" - -#include "debughandler.h" -#include "asserthandler.h" - - -static struct sockaddr_in net_local; -static uint8_t net_ibuf[NET_BUF_SIZE]; -static uint8_t net_obuf[NET_BUF_SIZE]; -static uint32_t net_ipos; -static uint32_t net_ilen; -static uint32_t net_opos; -int net_socket = 0; - - -void ipx2in(struct sockaddr_ipx *from, struct sockaddr_in *to) -{ - to->sin_family = AF_INET; - std::memcpy(&to->sin_addr.s_addr, from->sa_nodenum, 4); - std::memcpy(&to->sin_port, from->sa_nodenum + 4, 2); - to->sin_zero[0] = from->sa_netnum[1]; -} - - -void in2ipx(struct sockaddr_in *from, struct sockaddr_ipx *to) -{ - to->sa_family = AF_IPX; - *(DWORD *)&to->sa_netnum = 1; - to->sa_netnum[1] = from->sin_zero[0]; - std::memcpy(to->sa_nodenum, &from->sin_addr.s_addr, 4); - std::memcpy(to->sa_nodenum + 4, &from->sin_port, 2); -} - - -bool is_ipx_broadcast(struct sockaddr_ipx *addr) -{ - unsigned char ff[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - if (std::memcmp(addr->sa_netnum, ff, 4) == 0 || std::memcmp(addr->sa_nodenum, ff, 6) == 0) { - return true; - } else { - return false; - } -} - - -int net_opt_reuse() -{ - int yes = 1; - ::setsockopt(net_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)); - return yes; -} - - -int net_opt_broadcast() -{ - int yes = 1; - ::setsockopt(net_socket, SOL_SOCKET, SO_BROADCAST, (char *)&yes, sizeof(yes)); - return yes; -} - - -int net_address(struct sockaddr_in *addr, const char *host, uint16_t port) -{ - struct hostent *hent; - hent = ::gethostbyname(host); - if (!hent) { - DEBUG_ERROR("CnCNet4: gethostbyname failed!\n"); - return 0; - } - - net_address_ex(addr, *(int *)hent->h_addr_list[0], port); - return 1; -} - - -void net_address_ex(struct sockaddr_in *addr, uint32_t ip, uint16_t port) -{ - int size = sizeof(struct sockaddr_in); - std::memset(addr, 0, size); - addr->sin_family = AF_INET; - addr->sin_addr.s_addr = ip; - addr->sin_port = htons(port); -} - - -int net_init() -{ - WSADATA wsaData; - - if (net_socket) { - net_free(); - } - - WSAStartup(0x0101, &wsaData); - - net_socket = ::socket(AF_INET, SOCK_DGRAM, 0); - net_ipos = 0; - net_opos = 0; - return net_socket; -} - - -void net_free() -{ - ::closesocket(net_socket); - WSACleanup(); - net_socket = 0; -} - - -int net_bind(const char *ip, int port) -{ - if (!net_socket) { - return 0; - } - - net_address(&net_local, ip, port); - - return ::bind(net_socket, (struct sockaddr *)&net_local, sizeof(net_local)); -} - - -uint32_t net_read_size() -{ - return net_ilen - net_ipos; -} - - -int8_t net_read_int8() -{ - int8_t tmp; - if (net_ipos + 1 > net_ilen) { - return 0; - } - std::memcpy(&tmp, net_ibuf + net_ipos, 1); - net_ipos += 1; - return tmp; -} - - -int16_t net_read_int16() -{ - int16_t tmp; - if (net_ipos + 2 > net_ilen) { - return 0; - } - std::memcpy(&tmp, net_ibuf + net_ipos, 2); - net_ipos += 2; - return tmp; -} - - -int32_t net_read_int32() -{ - int32_t tmp; - if (net_ipos + 4 > net_ilen) { - return 0; - } - std::memcpy(&tmp, net_ibuf + net_ipos, 4); - net_ipos += 4; - return tmp; -} - - -int net_read_data(void *ptr, size_t len) -{ - if (net_ipos + len > net_ilen) { - len = net_ilen - net_ipos; - } - - std::memcpy(ptr, net_ibuf + net_ipos, len); - net_ipos += len; - return len; -} - - -int net_read_string(char *str, size_t len) -{ - int i; - for (i = net_ipos; i < NET_BUF_SIZE; i++) { - if (net_ibuf[i] == '\0') { - break; - } - } - - if (len > i - net_ipos) { - len = i - net_ipos; - } - - std::memcpy(str, net_ibuf + net_ipos, len); - str[len] = '\0'; - net_ipos += len + 1; - return 0; -} - - -int net_write_int8(int8_t d) -{ - ASSERT(net_opos + 1 <= NET_BUF_SIZE); - std::memcpy(net_obuf + net_opos, &d, 1); - net_opos += 1; - return 1; -} - - -int net_write_int16(int16_t d) -{ - ASSERT(net_opos + 2 <= NET_BUF_SIZE); - int16_t tmp = d; - std::memcpy(net_obuf + net_opos, &tmp, 2); - net_opos += 2; - return 1; -} - - -int net_write_int32(int32_t d) -{ - ASSERT(net_opos + 4 <= NET_BUF_SIZE); - int32_t tmp = d; - std::memcpy(net_obuf + net_opos, &tmp, 4); - net_opos += 4; - return 1; -} - - -int net_write_data(void *ptr, size_t len) -{ - ASSERT(net_opos + len <= NET_BUF_SIZE); - std::memcpy(net_obuf + net_opos, ptr, len); - net_opos += len; - return 1; -} - - -int net_write_string(char *str) -{ - ASSERT(net_opos + strlen(str) + 1 <= NET_BUF_SIZE); - std::memcpy(net_obuf + net_opos, str, strlen(str) + 1); - net_opos += strlen(str) + 1; - return 1; -} - - -int net_write_string_int32(int32_t d) -{ - char str[32]; - std::snprintf(str, sizeof(str), "%d", d); - return net_write_string(str); -} - - -int net_recv(struct sockaddr_in *src) -{ - socklen_t l = sizeof(struct sockaddr_in); - net_ipos = 0; - net_ilen = ::recvfrom(net_socket, (char *)net_ibuf, NET_BUF_SIZE, 0, (struct sockaddr *)src, &l); - return net_ilen; -} - - -int net_send(struct sockaddr_in *dst) -{ - int ret = net_send_noflush(dst); - net_send_discard(); - return ret; -} - - -int net_send_noflush(struct sockaddr_in *dst) -{ - int ret = ::sendto(net_socket, (char *)net_obuf, net_opos, 0, (struct sockaddr *)dst, sizeof(struct sockaddr_in)); - return ret; -} - - -void net_send_discard() -{ - net_opos = 0; -} diff --git a/src/cncnet/cncnet4/cncnet4_net.h b/src/cncnet/cncnet4/cncnet4_net.h deleted file mode 100644 index 862de3391..000000000 --- a/src/cncnet/cncnet4/cncnet4_net.h +++ /dev/null @@ -1,84 +0,0 @@ -/******************************************************************************* -/* O P E N S O U R C E -- V I N I F E R A ** -/******************************************************************************* - * - * @project Vinifera - * - * @file CNCNET4_NET.H - * - * @author CCHyper (Based on work by Toni Spets) - * - * @brief Network utility functions. - * - * @license Vinifera 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 - * 3 of the License, or (at your option) any later version. - * - * Vinifera 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 this program. - * If not, see . - * - ******************************************************************************/ -#pragma once - -#include -#include -#include -#include - - -#define NET_BUF_SIZE 2048 - -typedef int socklen_t; - -enum { - CMD_TUNNEL, - CMD_P2P, - CMD_DISCONNECT, - CMD_PING, - CMD_QUERY, - CMD_TESTP2P -}; - - -extern int net_socket; - -void ipx2in(struct sockaddr_ipx *from, struct sockaddr_in *to); -void in2ipx(struct sockaddr_in *from, struct sockaddr_ipx *to); -bool is_ipx_broadcast(struct sockaddr_ipx *addr); - -int net_opt_reuse(); -int net_opt_broadcast(); - -int net_address(struct sockaddr_in *addr, const char *host, uint16_t port); -void net_address_ex(struct sockaddr_in *addr, uint32_t ip, uint16_t port); - -int net_init(); -void net_free(); - -int net_bind(const char *ip, int port); - -uint32_t net_read_size(); -int8_t net_read_int8(); -int16_t net_read_int16(); -int32_t net_read_int32(); -int net_read_data(void *, size_t); -int net_read_string(char *str, size_t len); - -int net_write_int8(int8_t); -int net_write_int16(int16_t); -int net_write_int32(int32_t); -int net_write_data(void *, size_t); -int net_write_string(char *str); -int net_write_string_int32(int32_t); - -int net_recv(struct sockaddr_in *); -int net_send(struct sockaddr_in *); -int net_send_noflush(struct sockaddr_in *dst); -void net_send_discard(); diff --git a/src/cncnet/cncnet5/cncnet5_hooks.cpp b/src/cncnet/cncnet5/cncnet5_hooks.cpp deleted file mode 100644 index e4556a5d1..000000000 --- a/src/cncnet/cncnet5/cncnet5_hooks.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/******************************************************************************* -/* O P E N S O U R C E -- V I N I F E R A ** -/******************************************************************************* - * - * @project Vinifera - * - * @file CNCNET5_HOOKS.CPP - * - * @author CCHyper - * - * @brief Contains the hooks for implementing the CnCNet5 system. - * - * @license Vinifera 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 - * 3 of the License, or (at your option) any later version. - * - * Vinifera 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 this program. - * If not, see . - * - ******************************************************************************/ -#include "cncnet5_hooks.h" -#include "cncnet5_globals.h" -#include "cncnet5_wspudp.h" -#include "wsproto.h" -#include "wspipx.h" -#include "wspudp.h" -#include "tibsun_globals.h" -#include "session.h" -#include "debughandler.h" -#include "asserthandler.h" -#include "hooker.h" -#include "hooker_macros.h" - - -/** - * #issue-69 - * - * Create the CnCNet5 UDP interface or standard UDP interface depending - * on if the CnCNet5 system has been enabled. - * - * @author: CCHyper - */ -static void Create_PacketTransport() -{ - if (CnCNet5::IsActive && CnCNet5::TunnelInfo.Is_Valid()) { - PacketTransport = new CnCNet5UDPInterfaceClass( - CnCNet5::TunnelInfo.ID, - CnCNet5::TunnelInfo.IP, - CnCNet5::TunnelInfo.Port, - CnCNet5::TunnelInfo.PortHack); - if (!PacketTransport) { - DEBUG_ERROR("Failed to create PacketTransport for CnCNet5!\n"); - } - - } else { - PacketTransport = new UDPInterfaceClass(); - if (!PacketTransport) { - DEBUG_ERROR("Failed to create PacketTransport!\n"); - } - } -} - - -/** - * #issue-69 - * - * This patch replaces the call to the UDPInterfaceClass constructor when - * setting up the PacketTransport for network multiplayer games with - * conditional code that creates the CnCNet5 interface is enabled. - * - * @author: CCHyper - */ -DECLARE_PATCH(_Select_Game_Create_PacketTransport_Patch) -{ - Create_PacketTransport(); - - Session.CommProtocol = COMM_PROTOCOL_SINGLE_NO_COMP; - - _asm { mov eax, [0x0074C8D8] } // PacketProtocol - _asm { mov eax, [eax] } - - JMP_REG(edx, 0x004E2436); -} - - -/** - * Main function for patching the hooks. - */ -void CnCNet5_Hooks() -{ - Patch_Jump(0x004E2406, &_Select_Game_Create_PacketTransport_Patch); -} diff --git a/src/extensions/building/buildingext_hooks.cpp b/src/extensions/building/buildingext_hooks.cpp index bcab9f369..b53a7b353 100644 --- a/src/extensions/building/buildingext_hooks.cpp +++ b/src/extensions/building/buildingext_hooks.cpp @@ -37,6 +37,7 @@ #include "buildingtypeext.h" #include "unit.h"; #include "unitext.h" +#include "factory.h" #include "technotype.h" #include "technotypeext.h" #include "aircraft.h" @@ -67,6 +68,7 @@ #include "hooker.h" #include "hooker_macros.h" +#include "spawner.h" /** @@ -84,6 +86,7 @@ static class BuildingClassExt final : public BuildingClass const InfantryTypeClass* _Crew_Type() const; int _How_Many_Survivors() const; int _Shape_Number() const; + void _Draw_Overlays(Point2D& coord, Rect& rect); }; @@ -270,6 +273,118 @@ int BuildingClassExt::_Shape_Number() const shapenum += largest; } } + } +} + + +/** + * Reimplements the BuildingClass::Draw_Overlays function. + * + * @author: ZivDero + */ +void BuildingClassExt::_Draw_Overlays(Point2D& coord, Rect& rect) +{ + if (BState != BSTATE_CONSTRUCTION) + { + /** + * Draw the repair animation. + */ + if (IsRepairing) + { + if (!Map.Is_Shrouded(Center_Coord()) && !(Scen->SpecialFlags.IsFogOfWar || IsFogged) && Visual_Character() != VISUAL_HIDDEN) + { + Point2D xy = coord; + if (!IsPowerOn) + xy -= Point2D(5, 5); + + int delay = Options.Normalize_Delay(14) / 4; + if (delay < 2) + delay = 2; + + CC_Draw_Shape(LogicSurface, MouseDrawer, WrenchShape, 6 * (Frame % delay) / (delay - 1), &xy, &rect, SHAPE_ALPHA | SHAPE_WIN_REL | SHAPE_CENTER); + } + } + + /** + * Draw the power off animation. + */ + if (!IsPowerOn && House->Is_Player_Control()) + { + if (!Map.Is_Shrouded(Center_Coord()) && !(Scen->SpecialFlags.IsFogOfWar || IsFogged)) + { + Point2D xy = coord; + if (IsRepairing) + xy += Point2D(10, 10); + + int delay = Options.Normalize_Delay(14) / 4; + if (delay < 2) + delay = 2; + + CC_Draw_Shape(LogicSurface, MouseDrawer, PowerOffShape, 6 * (Frame % delay) / (delay - 1), &xy, &rect, SHAPE_ALPHA | SHAPE_WIN_REL | SHAPE_CENTER); + } + } + + if (IsSelected) + { + /** + * Draw the primary factory pip. + */ + if (House->Is_Ally(PlayerPtr) || SpiedBy & (1 << (PlayerPtr->Class->House)) || (Spawner::Active && Spawner::Get_Config()->Houses[PlayerPtr->Get_Heap_ID()].IsSpectator)) + { + Point2D xy(coord.X - 10, coord.Y + 10); + entry_338(xy, coord, rect); + } + + /** + * If this is a factory, and the player has spied its owner, draw the cameo of what it's currently producing. + */ + if (SpiedBy & (1 << (PlayerPtr->Class->House)) || (Spawner::Active && Spawner::Get_Config()->Houses[PlayerPtr->Get_Heap_ID()].IsSpectator)) + { + FactoryClass* factory = House->Is_Human_Control() ? House->Fetch_Factory(Class->ToBuild) : Factory; + if (factory != nullptr) + { + ObjectClass* obj = factory->Get_Object(); + if (obj != nullptr) + { + /** + * #issue-487 + * + * Adds support for PCX/PNG cameo icons. + * + * @author: CCHyper + */ + const auto technotypeext = Extension::Fetch(obj->Techno_Type_Class()); + if (technotypeext->CameoImageSurface) + { + /** + * Draw the cameo pcx image. + */ + Rect pcxrect; + pcxrect.X = rect.X + coord.X; + pcxrect.Y = rect.Y + coord.Y; + pcxrect.Width = technotypeext->CameoImageSurface->Get_Width(); + pcxrect.Height = technotypeext->CameoImageSurface->Get_Height(); + + SpriteCollection.Draw(pcxrect, *LogicSurface, *technotypeext->CameoImageSurface); + } + else + { + const ShapeFileStruct* shape = obj->Techno_Type_Class()->Get_Cameo_Data(); + + /** + * Draw the cameo shape. + * + * Original code used NormalDrawer, which is the old Red Alert shape + * drawer, so we need to use CameoDrawer here for the correct palette. + */ + CC_Draw_Shape(LogicSurface, CameoDrawer, shape, 0, &coord, &rect, SHAPE_ALPHA | SHAPE_WIN_REL | SHAPE_CENTER); + } + } + } + } + } + } +} return shapenum; } @@ -771,62 +886,6 @@ DECLARE_PATCH(_BuildingClass_Explode_ShakeScreen_Division_BugFix_Patch) } -/** - * #issue-72 - * - * Fixes the bug where the wrong palette used to draw the cameo of the object - * being produced above a enemy spied factory building. - * - * @author: CCHyper - */ -DECLARE_PATCH(_BuildingClass_Draw_Spied_Cameo_Palette_Patch) -{ - GET_REGISTER_STATIC(TechnoClass *, factory_obj, eax); - GET_REGISTER_STATIC(Point2D *, pos_xy, edi); - GET_REGISTER_STATIC(Rect *, window_rect, ebp); - static TechnoTypeClass *technotype; - static TechnoTypeClassExtension *technotypeext; - static const ShapeFileStruct *cameo_shape; - static Surface *pcx_image; - static Rect pcxrect; - - technotype = factory_obj->Techno_Type_Class(); - - /** - * #issue-487 - * - * Adds support for PCX/PNG cameo icons. - * - * @author: CCHyper - */ - technotypeext = Extension::Fetch(technotype); - if (technotypeext->CameoImageSurface) { - - /** - * Draw the cameo pcx image. - */ - pcxrect.X = window_rect->X + pos_xy->X; - pcxrect.Y = window_rect->Y + pos_xy->Y; - pcxrect.Width = technotypeext->CameoImageSurface->Get_Width(); - pcxrect.Height = technotypeext->CameoImageSurface->Get_Height(); - - SpriteCollection.Draw(pcxrect, *LogicSurface, *technotypeext->CameoImageSurface); - - } else { - - cameo_shape = technotype->Get_Cameo_Data(); - - /** - * Draw the cameo shape. - * - * Original code used NormalDrawer, which is the old Red Alert shape - * drawer, so we need to use CameoDrawer here for the correct palette. - */ - CC_Draw_Shape(LogicSurface, CameoDrawer, cameo_shape, 0, pos_xy, window_rect, ShapeFlagsType(SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_ALPHA|SHAPE_NORMAL)); - } - - JMP(0x00428B13); -} /** @@ -1060,7 +1119,6 @@ void BuildingClassExtension_Hooks() */ BuildingClassExtension_Init(); - Patch_Jump(0x00428AD3, &_BuildingClass_Draw_Spied_Cameo_Palette_Patch); Patch_Jump(0x0042B250, &_BuildingClass_Explode_ShakeScreen_Division_BugFix_Patch); Patch_Jump(0x00433BB5, &_BuildingClass_Mission_Open_Gate_Open_Sound_Patch); Patch_Jump(0x00433C6F, &_BuildingClass_Mission_Open_Gate_Close_Sound_Patch); @@ -1084,4 +1142,5 @@ void BuildingClassExtension_Hooks() Patch_Jump(0x0042F799, &_BuildingClass_Captured_DontScore_Patch); Patch_Jump(0x0042E5F5, &_BuildingClass_Grand_Opening_Assign_FreeUnit_LastDockedBuilding_Patch); //Patch_Jump(0x00429220, &BuildingClassExt::_Shape_Number); // It's identical to vanilla, leaving it in in case it's ever needed + Patch_Jump(0x00428810, &BuildingClassExt::_Draw_Overlays); } diff --git a/src/extensions/extension.cpp b/src/extensions/extension.cpp index 7925d5fdf..6684bfaa0 100644 --- a/src/extensions/extension.cpp +++ b/src/extensions/extension.cpp @@ -139,6 +139,7 @@ #include "warheadtypeext.h" #include "waveext.h" #include "weapontypeext.h" +#include "viniferaevent.h" #include "rulesext.h" #include "scenarioext.h" @@ -1144,8 +1145,8 @@ static bool Print_Event_List(FILE *fp, QueueClass &list) if (ev) { char ev_byte_format[4]; Wstring ev_data_buffer; - int ev_size = EventClass::Event_Length(ev->Type); - const char *ev_name = EventClass::Event_Name(ev->Type); + int ev_size = ViniferaEventClass::Event_Length(ev->Type); + const char *ev_name = ViniferaEventClass::Event_Name(ev->Type); for (int i = 0; i < ev_size; ++i) { std::snprintf(ev_byte_format, sizeof(ev_byte_format), "%02X", (unsigned char)ev->Data.Array.Byte[i]); // We use this union member so we can do array access. ev_data_buffer += ev_byte_format; diff --git a/src/extensions/extension_hooks.cpp b/src/extensions/extension_hooks.cpp index 361e2ff06..24bc71704 100644 --- a/src/extensions/extension_hooks.cpp +++ b/src/extensions/extension_hooks.cpp @@ -153,6 +153,7 @@ #include "hooker.h" #include "hooker_macros.h" #include "spawnmanager_hooks.h" +#include "viniferaevent_hooks.h" void Extension_Hooks() @@ -282,6 +283,7 @@ void Extension_Hooks() MultiScoreExtension_Hooks(); ScoreClassExtension_Hooks(); MultiMissionExtension_Hooks(); + ViniferaEvent_Hooks(); /** * Dialogs and associated code. diff --git a/src/extensions/init/initext_hooks.cpp b/src/extensions/init/initext_hooks.cpp index 20aa9922f..9a2791e08 100644 --- a/src/extensions/init/initext_hooks.cpp +++ b/src/extensions/init/initext_hooks.cpp @@ -1029,6 +1029,6 @@ void GameInit_Hooks() /** * TS Client file structure assumes Firestorm is always installed and enabled. */ - //Patch_Jump(0x00407050, &Vinifera_Detect_Addons); + Patch_Jump(0x00407050, &Vinifera_Detect_Addons); #endif } diff --git a/src/extensions/scenario/scenarioext.cpp b/src/extensions/scenario/scenarioext.cpp index 9553313e7..3607ccb84 100644 --- a/src/extensions/scenario/scenarioext.cpp +++ b/src/extensions/scenario/scenarioext.cpp @@ -47,6 +47,7 @@ #include "vinifera_saveload.h" #include "asserthandler.h" #include "debughandler.h" +#include "spawner.h" /** @@ -652,8 +653,8 @@ void ScenarioClassExtension::Assign_Houses() */ housep = new HouseClass(HouseTypes[node.Player.House]); - std::memset((char *)housep->IniName, 0, MPLAYER_NAME_MAX); - std::strncpy((char *)housep->IniName, node.Name, MPLAYER_NAME_MAX-1); + std::memset(housep->IniName, 0, MPLAYER_NAME_MAX); + std::strncpy(housep->IniName, node.Name, MPLAYER_NAME_MAX-1); /** * Set the house's IsHuman, Credits, ActLike, and RemapTable. @@ -694,52 +695,60 @@ void ScenarioClassExtension::Assign_Houses() */ for (int i = Session.Players.Count(); i < Session.Players.Count() + Session.Options.AIPlayers; ++i) { + if (!Spawner::Active) + { #if 0 - if (Percent_Chance(50)) { - pref_house = HOUSE_GDI; - } else { - pref_house = HOUSE_NOD; - } + if (Percent_Chance(50)) { + pref_house = HOUSE_GDI; + } + else { + pref_house = HOUSE_NOD; + } #endif - /** - * #issue-7 - * - * Replaces code from above. - * - * Fixes a limitation where the AI would only be able to choose - * between the houses GDI (0) and NOD (1). Now, all houses that - * have "IsMultiplay" true will be considered for sellection. - */ - while (true) { - pref_house = (HousesType)Random_Pick(0, HouseTypes.Count()-1); - if (HouseTypes[pref_house]->IsMultiplay) { - break; + /** + * #issue-7 + * + * Replaces code from above. + * + * Fixes a limitation where the AI would only be able to choose + * between the houses GDI (0) and NOD (1). Now, all houses that + * have "IsMultiplay" true will be considered for sellection. + */ + while (true) { + pref_house = (HousesType)Random_Pick(0, HouseTypes.Count() - 1); + if (HouseTypes[pref_house]->IsMultiplay) { + break; + } } - } - /** - * Pick a color for this house; keep looping until we find one. - */ - while (true) { - color = Random_Pick(0, (MAX_PLAYERS-1)); - if (color_used[color] == false) { - break; + /** + * Pick a color for this house; keep looping until we find one. + */ + while (true) { + color = Random_Pick(0, (MAX_PLAYERS - 1)); + if (color_used[color] == false) { + break; + } } + color_used[color] = true; + } + else + { + color = Spawner::Get_Config()->Players[i].Color; + pref_house = static_cast(Spawner::Get_Config()->Players[i].House); } - color_used[color] = true; housep = new HouseClass(HouseTypes[pref_house]); /** - * Set the house's IsHuman, Credits, ActLike, and RemapTable. + * Set the house's IsHuman, Credits, ActLike, and RemapColor. */ housep->IsHuman = false; - //housep->IsStarted = true; housep->Control.TechLevel = BuildLevel; - housep->Init_Data((PlayerColorType)color, pref_house, Session.Options.Credits); - housep->RemapColor = Session.Player_Color_To_Scheme_Color((PlayerColorType)color); + housep->Init_Data(static_cast(color), pref_house, Session.Options.Credits); + housep->RemapColor = Session.Player_Color_To_Scheme_Color(static_cast(color)); housep->Init_Remap_Color(); std::strcpy(housep->IniName, Text_String(TXT_COMPUTER)); @@ -751,7 +760,7 @@ void ScenarioClassExtension::Assign_Houses() DiffType difficulty = Scen->CDifficulty; if (Session.Players.Count() > 1 && Rule->IsCompEasyBonus && difficulty > DIFF_EASY) { - difficulty = (DiffType)(difficulty - 1); + difficulty = static_cast(difficulty - 1); } housep->Assign_Handicap(difficulty); @@ -819,6 +828,69 @@ void ScenarioClassExtension::Assign_Houses() housep->Init_Remap_Color(); } + if (Spawner::Active) + { + const int house_count = std::min(Houses.Count(), (int)std::size(Spawner::Get_Config()->Houses)); + for (int i = 0; i < house_count; i++) + { + housep = Houses[i]; + + if (housep->Class->IsMultiplayPassive) + continue; + + const auto house_config = &Spawner::Get_Config()->Houses[i]; + + // Set Alliances + for (char j = 0; j < (char)std::size(house_config->Alliances); ++j) + { + const int ally_index = house_config->Alliances[j]; + if (ally_index != -1) + housep->Allies &= 1 << ally_index; + } + + constexpr char* AINamesByDifficultyArray[5] = { + "Hard AI", + "Medium AI", + "Easy AI"//, + //"Brutal AI", + //"Ultimate AI" + }; + + // Set Handicap and Names for AI + { + const auto player_config = &Spawner::Get_Config()->Players[i]; + + if (player_config->Difficulty >= 0 && player_config->Difficulty < std::size(AINamesByDifficultyArray)) + { + housep->Assign_Handicap(static_cast(player_config->Difficulty)); + if (Spawner::Get_Config()->AINamesByDifficulty && !housep->IsHuman) + { + std::strcpy(housep->IniName, AINamesByDifficultyArray[player_config->Difficulty]); + } + } + } + + // Set Spectators + enum + { + SPAWN_OBSERVER = -1, + SPAWN_OBSERVER_ALT = 90 + }; + + const int spawn_loc = house_config->SpawnLocation; + const bool is_spectator = housep->IsHuman && + (house_config->IsSpectator + || spawn_loc == SPAWN_OBSERVER + || spawn_loc == SPAWN_OBSERVER_ALT); + + // Spectators are considered defeated + if (is_spectator) + { + housep->IsDefeated = true; + } + } + } + DEBUG_INFO("Assign_Houses(exit)\n"); } @@ -1179,11 +1251,18 @@ static DynamicVectorClass Build_Starting_Waypoint_List(bool official) * if there are 4 or fewer players. Unofficial maps will pick from all the * available waypoints. */ - int look_for = std::max(min_waypts, Session.Players.Count()+Session.Options.AIPlayers); + int look_for = std::max(min_waypts, Session.Players.Count() + Session.Options.AIPlayers); if (!official) { look_for = MAX_PLAYERS; } + if (Spawner::Active) { + for (int i = 0; i < Session.Players.Count() + Session.Options.AIPlayers; i++) { + if (Spawner::Get_Config()->Houses[i].IsSpectator) + look_for--; + } + } + for (int waycount = 0; waycount < look_for; ++waycount) { if (Scen->Is_Valid_Waypoint(waycount)) { Cell waycell = Scen->Get_Waypoint_Location(waycount); @@ -1303,6 +1382,11 @@ void ScenarioClassExtension::Create_Units(bool official) continue; } + if (Spawner::Active && hptr->IsDefeated) { + DEV_DEBUG_INFO("House %d is a spectator, skipping.\n", house); + continue; + } + DynamicVectorClass available_infantry; DynamicVectorClass available_units; @@ -1372,66 +1456,97 @@ void ScenarioClassExtension::Create_Units(bool official) } } - /** - * Pick the starting location for this house. The first house just picks - * one of the valid locations at random. The other houses pick the furthest - * waypoint from the existing houses. - */ - if (numtaken == 0) { - int pick = Random_Pick(0, waypts.Count()-1); - centroid = waypts[pick]; - taken[pick] = true; - numtaken++; - - } else { + bool pick_random = true; + if (Spawner::Active) { + enum { + SPAWN_RANDOM = -2 + }; + + int chosen_spawn = Spawner::Get_Config()->Houses[hptr->Get_Heap_ID()].SpawnLocation; + + if (chosen_spawn != SPAWN_RANDOM) { + chosen_spawn = std::clamp(chosen_spawn, 0, 7); + if (!taken[chosen_spawn]) { + centroid = waypts[chosen_spawn]; + taken[chosen_spawn] = true; + pick_random = false; + numtaken++; + } + } + } - /** - * Set all waypoints to have a score of zero in preparation for giving - * a distance score to all waypoints. - */ - int score[MAX_STORED_WAYPOINTS]; - std::memset(score, '\0', sizeof(score)); + + if (pick_random) { /** - * Scan through all waypoints and give a score as a value of the sum - * of the distances from this waypoint to all taken waypoints. + * Pick the starting location for this house. The first house just picks + * one of the valid locations at random. The other houses pick the furthest + * waypoint from the existing houses. */ - for (int index = 0; index < waypts.Count(); index++) { + if (numtaken == 0) { + int pick = Random_Pick(0, waypts.Count() - 1); + centroid = waypts[pick]; + taken[pick] = true; + numtaken++; + + if (Spawner::Active) { + Spawner::Get_Config()->Houses[hptr->Class->ID].SpawnLocation = pick; + } + + } + else { + + /** + * Set all waypoints to have a score of zero in preparation for giving + * a distance score to all waypoints. + */ + int score[MAX_STORED_WAYPOINTS]; + std::memset(score, '\0', sizeof(score)); /** - * If this waypoint has not already been taken, then accumulate the - * sum of the distance between this waypoint and all other taken - * waypoints. + * Scan through all waypoints and give a score as a value of the sum + * of the distances from this waypoint to all taken waypoints. */ - if (!taken[index]) { - for (int trypoint = 0; trypoint < waypts.Count(); trypoint++) { + for (int index = 0; index < waypts.Count(); index++) { - if (taken[trypoint]) { - score[index] += Distance(waypts[index], waypts[trypoint]); + /** + * If this waypoint has not already been taken, then accumulate the + * sum of the distance between this waypoint and all other taken + * waypoints. + */ + if (!taken[index]) { + for (int trypoint = 0; trypoint < waypts.Count(); trypoint++) { + + if (taken[trypoint]) { + score[index] += Distance(waypts[index], waypts[trypoint]); + } } } } - } - /** - * Now find the waypoint with the largest score. This waypoint is the one - * that is furthest from all other taken waypoints. - */ - int best = 0; - int bestvalue = 0; - for (int searchindex = 0; searchindex < waypts.Count(); searchindex++) { - if (score[searchindex] > bestvalue || bestvalue == 0) { - bestvalue = score[searchindex]; - best = searchindex; + /** + * Now find the waypoint with the largest score. This waypoint is the one + * that is furthest from all other taken waypoints. + */ + int best = 0; + int bestvalue = 0; + for (int searchindex = 0; searchindex < waypts.Count(); searchindex++) { + if (score[searchindex] > bestvalue || bestvalue == 0) { + bestvalue = score[searchindex]; + best = searchindex; + } } - } - /** - * Assign this best position to the house. - */ - centroid = waypts[best]; - taken[best] = true; - numtaken++; + /** + * Assign this best position to the house. + */ + centroid = waypts[best]; + taken[best] = true; + numtaken++; + + if (Spawner::Active) + Spawner::Get_Config()->Houses[hptr->Class->ID].SpawnLocation = best; + } } /** diff --git a/src/extensions/scenario/scenarioext_hooks.cpp b/src/extensions/scenario/scenarioext_hooks.cpp index 16dc762ac..f6daa8685 100644 --- a/src/extensions/scenario/scenarioext_hooks.cpp +++ b/src/extensions/scenario/scenarioext_hooks.cpp @@ -311,11 +311,6 @@ void ScenarioClassExtension_Hooks() */ ScenarioClassExtension_Init(); - /** - * For compatibility with the TS Client we need to remove - * these two reimplementations as they conflict with the spawner. - */ -#if !defined(TS_CLIENT) /** * Hooks in the new Assign_Houses() function. * @@ -331,7 +326,6 @@ void ScenarioClassExtension_Hooks() * @author: CCHyper */ Patch_Call(0x005DD320, &ScenarioClassExtension::Create_Units); -#endif Patch_Jump(0x005DC9D4, &_Do_Win_Skip_MPlayer_Score_Screen_Patch); Patch_Jump(0x005DCD92, &_Do_Lose_Skip_MPlayer_Score_Screen_Patch); diff --git a/src/hooker/setup_hooks.cpp b/src/hooker/setup_hooks.cpp index 2851cd6ec..81a650626 100644 --- a/src/hooker/setup_hooks.cpp +++ b/src/hooker/setup_hooks.cpp @@ -36,8 +36,6 @@ #include "vinifera_hooks.h" #include "newswizzle_hooks.h" #include "extension_hooks.h" -#include "cncnet4_hooks.h" -#include "cncnet5_hooks.h" #include "sidebarext_hooks.h" @@ -50,9 +48,6 @@ void Setup_Hooks() Vinifera_Hooks(); NewSwizzle_Hooks(); Extension_Hooks(); - - CnCNet4_Hooks(); - CnCNet5_Hooks(); } /** diff --git a/src/new/viniferaevent/viniferaevent.cpp b/src/new/viniferaevent/viniferaevent.cpp new file mode 100644 index 000000000..4f9f3e3dd --- /dev/null +++ b/src/new/viniferaevent/viniferaevent.cpp @@ -0,0 +1,108 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file VINIFERAEVENT.CPP + * + * @author ZivDero, Belonit + * + * @brief Class that mimics vanilla EventClass to allow the creation + * of new events in Vinifera. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "viniferaevent.h" + +#include "asserthandler.h" +#include "protocolzero.h" + +/** + * Arrays with new event lengths and names. + */ +unsigned char ViniferaEventLength[VEVENT_COUNT - EVENT_COUNT] +{ + sizeof(ViniferaEventClass::Data.ResponseTime2) +}; + +const char* ViniferaEventNames[VEVENT_COUNT - EVENT_COUNT] +{ + "RESPONSE_TIME_2" +}; + + +/** + * Execute our new event. + * + * @author: ZivDero + */ +void ViniferaEventClass::Execute() +{ + switch (Type) + { + case VEVENT_RESPONSE_TIME_2: + ProtocolZero::Handle_Response_Time(this); + break; + + default: + break; + } +} + + +/** + * Get the length of this event. + * + * @author: ZivDero + */ +unsigned char ViniferaEventClass::Event_Length(ViniferaEventType type) +{ + ASSERT(type >= 0 && type < VEVENT_COUNT); + + if (type < EVENT_COUNT) + return EventClass::Event_Length(static_cast(type)); + + return ViniferaEventLength[type - EVENT_COUNT]; +} + + +/** + * Get the name of this event. + * + * @author: ZivDero + */ +const char* ViniferaEventClass::Event_Name(ViniferaEventType type) +{ + ASSERT(type >= 0 && type < VEVENT_COUNT); + + if (type < EVENT_COUNT) + return EventClass::Event_Name(static_cast(type)); + + return ViniferaEventNames[type - EVENT_COUNT]; +} + + +/** + * Check if this event was added by Vinifera. + * + * @author: ZivDero + */ +bool ViniferaEventClass::Is_Vinifera_Event(ViniferaEventType type) +{ + return (type >= VEVENT_FIRST && type < VEVENT_COUNT); +} diff --git a/src/new/viniferaevent/viniferaevent.h b/src/new/viniferaevent/viniferaevent.h new file mode 100644 index 000000000..8a5fdbd10 --- /dev/null +++ b/src/new/viniferaevent/viniferaevent.h @@ -0,0 +1,88 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file VINIFERAEVENT.H + * + * @author ZivDero, Belonit + * + * @brief Class that mimics vanilla EventClass to allow the creation + * of new events in Vinifera. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ +#pragma once + +#include +#include + +#include "event.h" +#include "latencylevel.h" +#include "tibsun_defines.h" + +enum ViniferaEventType : unsigned char +{ + VEVENT_RESPONSE_TIME_2 = EVENT_COUNT, // Start after the last vanilla event + + VEVENT_COUNT, + VEVENT_FIRST = VEVENT_RESPONSE_TIME_2 +}; + + +extern unsigned char ViniferaEventLength[VEVENT_COUNT - EVENT_COUNT]; +extern const char* ViniferaEventNames[VEVENT_COUNT - EVENT_COUNT]; + + +class ViniferaEventClass +{ +#pragma pack(push, 1) +public: + ViniferaEventType Type; + unsigned Frame; + bool IsExecuted; + int ID; + + union + { + char DataBuffer[36]; + + struct ResponseTime2 + { + unsigned char MaxAhead; + LatencyLevelEnum LatencyLevel; + } ResponseTime2; + + } Data; + + void Execute(); + EventClass& As_Event() { return *reinterpret_cast(this); } + + static unsigned char Event_Length(ViniferaEventType type); + static unsigned char Event_Length(EventType type) { return Event_Length(static_cast(type)); } + + static const char* Event_Name(ViniferaEventType type); + static const char* Event_Name(EventType type) { return Event_Name(static_cast(type)); } + + static bool Is_Vinifera_Event(ViniferaEventType type); +#pragma pack(pop) +}; + + +static_assert(sizeof(ViniferaEventClass) == sizeof(EventClass), "ViniferaEventClass doesn't match EventClass in size!"); +static_assert(sizeof(ViniferaEventClass::Data) == sizeof(EventClass::Data), "ViniferaEventClass::Data doesn't match EventClass::Data in size!"); +static_assert(offsetof(ViniferaEventClass, Data) == offsetof(EventClass, Data), "ViniferaEventClass Data is misplaced!"); diff --git a/src/new/viniferaevent/viniferaevent_hooks.cpp b/src/new/viniferaevent/viniferaevent_hooks.cpp new file mode 100644 index 000000000..7f4ddd8ca --- /dev/null +++ b/src/new/viniferaevent/viniferaevent_hooks.cpp @@ -0,0 +1,289 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file VINIFERAEVENT_HOOKS.H + * + * @author ZivDero + * + * @brief Contains the hooks for the Vinifera event class. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "viniferaevent_hooks.h" + +#include "hooker.h" +#include "hooker_macros.h" +#include "viniferaevent.h" + + +/** + * Patch EventClass::Execute to execute our new events. + * + * @author: ZivDero + */ +DECLARE_PATCH(_EventClass_Execute_ViniferaEvent) +{ + GET_REGISTER_STATIC(ViniferaEventClass*, vevent, esi); + static EventType eventtype; + static int id; + + _asm pushad + + if (ViniferaEventClass::Is_Vinifera_Event(vevent->Type)) + { + vevent->Execute(); + _asm popad + JMP(0x00495110); // return from function + } + + eventtype = static_cast(vevent->Type); + id = vevent->ID; + + _asm popad + + // Stolen instructions + _asm mov al, eventtype + _asm mov edi, id + JMP_REG(ebx, 0x00494299); +} + + +/** + * Patch event length in Add_Compressed_Events. + * + * @author: ZivDero + */ +DECLARE_PATCH(_Add_Compressed_Events_ViniferaEvent_Length) +{ + GET_REGISTER_STATIC(unsigned char, eventtype, cl); + static unsigned char eventlength; + + _asm pushad + + if (ViniferaEventClass::Is_Vinifera_Event(static_cast(eventtype))) + { + eventlength = ViniferaEventClass::Event_Length(static_cast(eventtype)); + } + else + { + eventlength = EventClass::Event_Length(static_cast(eventtype)); + } + + if (eventtype == EVENT_ADDPLAYER) + { + _asm popad + _asm mov bl, eventlength + JMP_REG(esi, 0x005B45EA); + } + else + { + _asm popad + _asm mov bl, eventlength + JMP_REG(esi, 0x005B45F3); + } +} + + +/** + * Extract_Compressed_Events -- extracts events from a packet. + * + * @author: 11/21/1995 DRD - Created. + * ZivDero - Adjustments for Tiberian Sun. + */ +static int Vinifera_Extract_Compressed_Events(void* buf, int bufsize) +{ + int pos = 0; // current buffer parsing position + int leftover = bufsize; // # bytes left to process + EventClass* event; // event ptr for parsing buffer + int count = 0; // # events processed + int datasize = 0; // size of data to copy + EventClass eventdata; // stores Frame, ID, etc + unsigned char numunits = 0; // # units stored in compressed MegaMissions + + /** + * Clear work event structure. + */ + std::memset(&eventdata, 0, sizeof(EventClass)); + + /** + * Assume the first event is a FRAMEINFO event + * Init 'datasize' to the amount of data to copy, minus the EventType value + * For the 1st packet only, this will include all info before the Data + * union, plus the size of the FrameInfo structure, minus the EventType size. + */ + datasize = (offsetof(EventClass, Data) + sizeof(EventClass::Data.FrameInfo)) - sizeof(EventType); + event = reinterpret_cast(static_cast(buf) + pos); + + while (leftover >= datasize + (int)sizeof(EventType)) + { + /** + * Add event to the DoList, only if it's not a FRAMESYNC + * (but FRAMEINFO's do get added.) + */ + if (event->Type != EVENT_FRAMESYNC) + { + /** + * Initialize the common data from the FRAMEINFO event. + * keeping IsExecuted 0 + */ + if (event->Type == EVENT_FRAMEINFO) + { + eventdata.Frame = event->Frame; + eventdata.ID = event->ID; + + /** + * Adjust position past the common data. + */ + pos += offsetof(EventClass, Data) - sizeof(EventType); + leftover -= offsetof(EventClass, Data) - sizeof(EventType); + } + + /** + * If MEGAMISSION event get the number of units (events to generate). + */ + else if (event->Type == EVENT_MEGAMISSION) + { + numunits = *(static_cast(buf) + pos + sizeof(eventdata.Type)); + pos += sizeof(numunits); + leftover -= sizeof(numunits); + } + + /** + * Clear the union data portion of the event. + */ + memset(&eventdata.Data, 0, sizeof(eventdata.Data)); + eventdata.Type = event->Type; + datasize = ViniferaEventClass::Event_Length(eventdata.Type); + + switch (eventdata.Type) + { + case EVENT_RESPONSE_TIME: + memcpy(&eventdata.Data.FrameInfo.Delay, static_cast(buf) + pos + sizeof(EventType), datasize); + break; + + case EVENT_ADDPLAYER: + memcpy(&eventdata.Data.Variable.Size, static_cast(buf) + pos + sizeof(EventType), datasize); + + eventdata.Data.Variable.Pointer = new char[eventdata.Data.Variable.Size]; + memcpy(eventdata.Data.Variable.Pointer, static_cast(buf) + pos + sizeof(EventType) + datasize, eventdata.Data.Variable.Size); + + pos += eventdata.Data.Variable.Size; + leftover -= eventdata.Data.Variable.Size; + + break; + + case EVENT_MEGAMISSION: + memcpy(&eventdata.Data.MegaMission, static_cast(buf) + pos + sizeof(EventType), datasize); + + if (numunits > 1) + { + pos += datasize + sizeof(EventType); + leftover -= datasize + sizeof(EventType); + datasize = sizeof(eventdata.Data.MegaMission.Whom); + + while (numunits) + { + if (!DoList.Add(eventdata)) + return -1; + + /** + * Keep count of how many events we add to the queue. + */ + count++; + numunits--; + memcpy(&eventdata.Data.MegaMission.Whom, static_cast(buf) + pos, datasize); + + /** + * If one unit left fall thru to normal code. + */ + if (numunits == 1) + { + datasize -= sizeof(EventType); + break; + } + else + { + pos += datasize; + leftover -= datasize; + } + } + } + break; + + default: + memcpy(&eventdata.Data, static_cast(buf) + pos + sizeof(EventType), datasize); + break; + } + + if (!DoList.Add(eventdata)) + { + if (eventdata.Type == EVENT_ADDPLAYER) + delete[] eventdata.Data.Variable.Pointer; + + return -1; + } + + /** + * Keep count of how many events we add to the queue. + */ + count++; + + pos += datasize + sizeof(EventType); + leftover -= datasize + sizeof(EventType); + + if (leftover) + { + event = reinterpret_cast(static_cast(buf) + pos); + datasize = ViniferaEventClass::Event_Length(event->Type); + if (event->Type == EVENT_MEGAMISSION) + datasize += sizeof(numunits); + } + } + /** + * FRAMESYNC event: This >should< be the only event in the buffer, + * and it will be uncompressed. + */ + else + { + pos += datasize + sizeof(EventType); + leftover -= datasize + sizeof(EventType); + event = reinterpret_cast(static_cast(buf) + pos); + + /** + * Size of FRAMESYNC event - EventType size. + */ + datasize = offsetof(EventClass, Data) + sizeof(EventClass::Data.FrameInfo) - sizeof(EventType); + } + } + + return count; + +} + + +/** + * Main function for patching the hooks. + */ +void ViniferaEvent_Hooks() +{ + Patch_Jump(0x00494294, &_EventClass_Execute_ViniferaEvent); + Patch_Jump(0x005B45D5, &_Add_Compressed_Events_ViniferaEvent_Length); + Patch_Jump(0x005B4A40, &Vinifera_Extract_Compressed_Events); +} diff --git a/src/new/viniferaevent/viniferaevent_hooks.h b/src/new/viniferaevent/viniferaevent_hooks.h new file mode 100644 index 000000000..1f2f22708 --- /dev/null +++ b/src/new/viniferaevent/viniferaevent_hooks.h @@ -0,0 +1,31 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file VINIFERAEVENT_HOOKS.CPP + * + * @author ZivDero + * + * @brief Contains the hooks for the Vinifera event class. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ +#pragma once + + +void ViniferaEvent_Hooks(); diff --git a/src/spawner/modules/autosurrender_hooks.cpp b/src/spawner/modules/autosurrender_hooks.cpp new file mode 100644 index 000000000..976082433 --- /dev/null +++ b/src/spawner/modules/autosurrender_hooks.cpp @@ -0,0 +1,131 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file AUTOSURRENDER_HOOKS.H + * + * @author ZivDero + * + * @brief Contains the hooks for auto-surrender in multiplayer. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "autosurrender_hooks.h" + +#include "hooker.h" +#include "hooker_macros.h" +#include "spawner.h" +#include "session.h" +#include "tibsun_globals.h" + + +static bool PlayerHasSurrendered = false; + + +/** + * Force surrender on abort. + * + * @author: ZivDero + */ +DECLARE_PATCH(_Standard_Options_Dialog_HANDLER_AutoSurrender) +{ + if (Spawner::Active && Spawner::Get_Config()->AutoSurrender) + { + if (Session.Type == GAME_IPX && !PlayerHasSurrendered) + { + SpecialDialog = SDLG_SURRENDER; + JMP(0x004B6D2A); + } + } + + // Stolen bytes + if (Session.Type != GAME_INTERNET) + { + JMP(0x004B6D20); + } + + JMP(0x004B6D0D); +} + + +/** + * Force surrender on disconnection. + * + * @author: ZivDero + */ +DECLARE_PATCH(_EventClass_Execute_REMOVE_PLAYER_AutoSurrender) +{ + if (Spawner::Active && Spawner::Get_Config()->AutoSurrender) + { + if (Session.Type == GAME_IPX && Spawner::Get_Config()->AutoSurrender) + { + JMP(0x00494F16); + } + } + + // Stolen bytes + if (Session.Type != GAME_INTERNET) + { + JMP(0x00494F28); + } + + JMP(0x00494F0D); +} + + +/** + * Two patches to save whether the player has surrendered. + * + * @author: ZivDero + */ +DECLARE_PATCH(_Special_Dialog_Surrender1) +{ + PlayerHasSurrendered = true; + _asm mov eax, PlayerPtr + _asm mov eax, [eax] + JMP_REG(ecx, 0x004627F8); +} + + +DECLARE_PATCH(_Special_Dialog_Surrender2) +{ + GET_REGISTER_STATIC(bool, has_surrendered, al); + + PlayerHasSurrendered = has_surrendered; + + // Stolen bytes + if (has_surrendered) + { + JMP(0x00462842); + } + + JMP(0x004628E3) +} + + +/** + * Main function for patching the hooks. + */ +void AutoSurrender_Hooks() +{ + Patch_Jump(0x004B6D04, &_Standard_Options_Dialog_HANDLER_AutoSurrender); + Patch_Jump(0x00494F08, &_EventClass_Execute_REMOVE_PLAYER_AutoSurrender); + Patch_Jump(0x004627F3, &_Special_Dialog_Surrender1); + Patch_Jump(0x0046283A, &_Special_Dialog_Surrender2); +} diff --git a/src/spawner/modules/autosurrender_hooks.h b/src/spawner/modules/autosurrender_hooks.h new file mode 100644 index 000000000..a761c313c --- /dev/null +++ b/src/spawner/modules/autosurrender_hooks.h @@ -0,0 +1,31 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file AUTOSURRENDER_HOOKS.H + * + * @author ZivDero + * + * @brief Contains the hooks for auto-surrender in multiplayer. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ +#pragma once + + +void AutoSurrender_Hooks(); diff --git a/src/spawner/modules/quickmatch_hooks.cpp b/src/spawner/modules/quickmatch_hooks.cpp new file mode 100644 index 000000000..2c199ecdd --- /dev/null +++ b/src/spawner/modules/quickmatch_hooks.cpp @@ -0,0 +1,150 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file QUICKMATCH_HOOKS.CPP + * + * @author ZivDero + * + * @brief Contains the hooks for the quick match mode. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "quickmatch_hooks.h" + +#include "hooker.h" +#include "spawner.h" +#include "house.h" +#include "textprint.h" +#include "ipxmgr.h" +#include "session.h" + +#include "hooker_macros.h" + + +static const char* PLAYER = "Player"; + + +/** + * A fake class for implementing new member functions which allow + * access to the "this" pointer of the intended class. + * + * @note: This must not contain a constructor or destructor. + * + * @note: All functions must not be virtual and must also be prefixed + * with "_" to prevent accidental virtualization. + */ +class IPXManagerClassExt : public IPXManagerClass +{ +public: + char* _Connection_Name(int id); +}; + + +/** + * Hide the player names when the IPX manager is asked for it. + * + * @author: ZivDero + */ +char* IPXManagerClassExt::_Connection_Name(int id) +{ + if (Spawner::Active && Spawner::Get_Config()->QuickMatch) + { + return const_cast(PLAYER); + } + else + { + return IPXManagerClass::Connection_Name(id); + } +} + + +/** + * Hide the player names in the in the radar. + * + * @author: ZivDero + */ +static int __cdecl sprintf_RadarClass_Draw_Names_Wrapper(char* buffer, const char* format, char* str) +{ + if (Spawner::Active && Spawner::Get_Config()->QuickMatch) + { + return std::sprintf(buffer, "%s", PLAYER); + } + else + { + return std::sprintf(buffer, format, str); + } +} + + +/** + * Hide the player names in the on the progress screen. + * + * @author: ZivDero + */ +static Point2D Fancy_Text_Print_ProgressScreenClass_Draw_Graphics_Wrapper(const char* text, XSurface* surface, Rect* rect, Point2D* xy, ColorScheme* fore, unsigned back, TextPrintType flag) +{ + if (Spawner::Active && Spawner::Get_Config()->QuickMatch) + { + return Fancy_Text_Print(PLAYER, surface, rect, xy, fore, back, flag); + } + else + { + return Fancy_Text_Print(text, surface, rect, xy, fore, back, flag); + } +} + + +/** + * Hide the player anmes in the Kick Player dialog. + * + * @author: ZivDero + */ +DECLARE_PATCH(_Kick_Player_Dialog_SendMessage_Hide_Name) +{ + GET_REGISTER_STATIC(HWND, hWnd, ebp); + GET_REGISTER_STATIC(int, index, esi); + + _asm pushad + + if (Spawner::Active && Spawner::Get_Config()->QuickMatch) + { + SendMessageA(hWnd, WM_SETTEXT, 0, reinterpret_cast(PLAYER)); + } + else + { + SendMessageA(hWnd, WM_SETTEXT, 0, reinterpret_cast(Session.Players[index]->Name)); + } + + _asm popad + + JMP(0x005B4038); +} + + +/** + * Main function for patching the hooks. + */ +void QuickMatch_Hooks() +{ + Patch_Call(0x005B980E, &sprintf_RadarClass_Draw_Names_Wrapper); + Patch_Call(0x005ADC8F, &Fancy_Text_Print_ProgressScreenClass_Draw_Graphics_Wrapper); + Patch_Jump(0x005B4024, &_Kick_Player_Dialog_SendMessage_Hide_Name); + Patch_Call(0x00648EAE, &IPXManagerClassExt::_Connection_Name); +} diff --git a/src/cncnet/cncnet5/cncnet5_hooks.h b/src/spawner/modules/quickmatch_hooks.h similarity index 88% rename from src/cncnet/cncnet5/cncnet5_hooks.h rename to src/spawner/modules/quickmatch_hooks.h index 853ea9b0c..d63cf2ea0 100644 --- a/src/cncnet/cncnet5/cncnet5_hooks.h +++ b/src/spawner/modules/quickmatch_hooks.h @@ -4,11 +4,11 @@ * * @project Vinifera * - * @file CNCNET5_HOOKS.H + * @file QUICKMATCH_HOOKS.H * - * @author CCHyper + * @author ZivDero * - * @brief Contains the hooks for implementing the CnCNet5 system. + * @brief Contains the hooks for the quick match mode. * * @license Vinifera is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,4 +28,4 @@ #pragma once -void CnCNet5_Hooks(); +void QuickMatch_Hooks(); diff --git a/src/spawner/modules/savedgamesdir_hooks.cpp b/src/spawner/modules/savedgamesdir_hooks.cpp new file mode 100644 index 000000000..f4f573699 --- /dev/null +++ b/src/spawner/modules/savedgamesdir_hooks.cpp @@ -0,0 +1,219 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file SAVEDGAMESDIR_HOOKS.CPP + * + * @author ZivDero + * + * @brief Contains the hooks for "Saved Games" directory customization. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "savedgamesdir_hooks.h" + +//#include +// +//#include "debughandler.h" +//#include "hooker.h" +//#include "hooker_macros.h" +//#include "loadoptions.h" +//#include "saveload.h" +//#include "savever.h" +//#include "spawner.h" +// +// +//namespace SavedGames +//{ +// static char Buffer[PATH_MAX]; +// +// /** +// * Make sure the subdirectory exists, and create it if not +// * +// * @author: ZivDero +// */ +// bool Ensure_Folder_Exists(const char* path) +// { +// const DWORD attributes = GetFileAttributes(path); +// +// // If path doesn't exist or isn't a directory, try creating it +// if (attributes == INVALID_FILE_ATTRIBUTES || !(attributes & FILE_ATTRIBUTE_DIRECTORY)) +// { +// // Directory created or already exists +// if (CreateDirectory(path, nullptr) || GetLastError() == ERROR_ALREADY_EXISTS) +// return true; +// +// DEBUG_ERROR("Error: Could not create directory \"%s\". Error code: %d\n", path, GetLastError()); +// return false; +// } +// +// // Directory already exists +// return true; +// } +// +// +// /** +// * Format the path to contain the subdirectory. +// * +// * @author: Belonit +// */ +// inline void Format_Path(char* buffer, size_t buffer_size, const char* filename) +// { +// std::snprintf(buffer, buffer_size, "%s\\%s", Spawner::Get_Config()->SavedGamesDir, filename); +// } +// +// /** +// * Format the path to contain the subdirectory, if it doesn't already. +// * +// * @author: ZivDero +// */ +// inline void Check_And_Format_Path(char* buffer, size_t buffer_size, const char* filename) +// { +// std::strstr(filename, Spawner::Get_Config()->SavedGamesDir) ? std::snprintf(buffer, buffer_size, "%s", filename) : Format_Path(buffer, buffer_size, filename); +// } +//} +// +// +///** +// * A fake class for implementing new member functions which allow +// * access to the "this" pointer of the intended class. +// * +// * @note: This must not contain a constructor or destructor. +// * +// * @note: All functions must not be virtual and must also be prefixed +// * with "_" to prevent accidental virtualization. +// */ +//class LoadOptionsClassExt : public LoadOptionsClass +//{ +//public: +// bool _Delete_File(const char* filename); +//}; +// +// +///** +// * Make sure the file name contains the subdirectory when deleting games. +// * +// * @author: ZivDero +// */ +//bool LoadOptionsClassExt::_Delete_File(const char* filename) +//{ +// SavedGames::Check_And_Format_Path(SavedGames::Buffer, std::size(SavedGames::Buffer), filename); +// return DeleteFileA(SavedGames::Buffer); +//} +// +// +///** +// * Make sure the file name contains the subdirectory in LoadOptionsClass. +// * +// * @author: ZivDero +// */ +//int __cdecl sprintf_LoadOptionsClass_Wrapper1(char* buffer, const char*, int number, char* str) +//{ +// // First create the format string itself, using our custom folder, e. g. "Saved Games\SAVE%04lX.%3s" +// SavedGames::Format_Path(SavedGames::Buffer, std::size(SavedGames::Buffer), "SAVE%04lX.%3s"); +// +// // Now actually format the path +// return std::sprintf(buffer, SavedGames::Buffer, number, str); +//} +// +// +///** +// * Make sure the file name contains the subdirectory in LoadOptionsClass. +// * +// * @author: ZivDero +// */ +//int __cdecl sprintf_LoadOptionsClass_Wrapper2(char* buffer, const char*, char* str) +//{ +// // First create the format string itself, using our custom folder, e. g. "Saved Games\*.%3s" +// SavedGames::Format_Path(SavedGames::Buffer, std::size(SavedGames::Buffer), "*.%3s"); +// +// // Now actually format the path +// return std::sprintf(buffer, SavedGames::Buffer, str); +//} +// +// +///** +// * Make sure the file name contains the subdirectory when saving the game. +// * +// * @author: ZivDero +// */ +//void __cdecl DebugString_Save_Game_Wrapper(const char* format, char* file_name, const char* descr) +//{ +// // Print the string it was going to print +// DEBUG_INFO(format, file_name, descr); +// +// // Format the path +// SavedGames::Check_And_Format_Path(SavedGames::Buffer, std::size(SavedGames::Buffer), file_name); +// +// // Print it back to the original buffer +// std::sprintf(file_name, "%s", SavedGames::Buffer); +// +// // Make sure the subfolder exists +// SavedGames::Ensure_Folder_Exists(Spawner::Get_Config()->SavedGamesDir); +//} +// +// +///** +// * Make sure the file name contains the subdirectory when loading the game. +// * +// * @author: ZivDero +// */ +//void __cdecl DebugString_Load_Game_Wrapper(const char* format, char* file_name) +//{ +// // Print the string it was going to print +// DEBUG_INFO(format, file_name); +// +// // Format the path +// SavedGames::Check_And_Format_Path(SavedGames::Buffer, std::size(SavedGames::Buffer), file_name); +// +// // Print it back to the original buffer +// std::sprintf(file_name, "%s", SavedGames::Buffer); +//} +// +// +///** +// * Make sure the file name contains the subdirectory when reading savefile readers. +// * +// * @author: ZivDero +// */ +//bool Get_Savefile_Info_Wrapper(char* file_name, SaveVersionInfo& wwsaveload) +//{ +// SavedGames::Check_And_Format_Path(SavedGames::Buffer, std::size(SavedGames::Buffer), file_name); +// +// // Print it back to the original buffer +// std::sprintf(file_name, "%s", SavedGames::Buffer); +// +// return Get_Savefile_Info(file_name, wwsaveload); +//} +// +// +/** + * Main function for patching the hooks. + */ +void SavedGamesDir_Hooks() +{ + //Patch_Call(0x00505001, &sprintf_LoadOptionsClass_Wrapper1); + //Patch_Call(0x00505294, &sprintf_LoadOptionsClass_Wrapper1); + //Patch_Call(0x00505509, &sprintf_LoadOptionsClass_Wrapper2); + //Patch_Call(0x00505863, &sprintf_LoadOptionsClass_Wrapper2); + //Patch_Call(0x005D4FF5, &DebugString_Save_Game_Wrapper); + //Patch_Call(0x005D6922, &DebugString_Load_Game_Wrapper); + //Patch_Jump(0x00505A20, &LoadOptionsClassExt::_Delete_File); + //Patch_Call(0x00505A8A, &Get_Savefile_Info_Wrapper); +} diff --git a/src/spawner/modules/savedgamesdir_hooks.h b/src/spawner/modules/savedgamesdir_hooks.h new file mode 100644 index 000000000..2bd2f7033 --- /dev/null +++ b/src/spawner/modules/savedgamesdir_hooks.h @@ -0,0 +1,31 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file SAVEDGAMESDIR_HOOKS.H + * + * @author ZivDero + * + * @brief Contains the hooks for "Saved Games" directory customization. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ +#pragma once + + +void SavedGamesDir_Hooks(); diff --git a/src/spawner/modules/spectator_hooks.cpp b/src/spawner/modules/spectator_hooks.cpp new file mode 100644 index 000000000..d39bc5218 --- /dev/null +++ b/src/spawner/modules/spectator_hooks.cpp @@ -0,0 +1,344 @@ +/******************************************************************************* +/* O P E N S O U R C E -- T S + + ** +/******************************************************************************* + * + * @project TS++ + * + * @file SPECTATOR_HOOKS.CPP + * + * @authors ZivDero + * + * @brief Contains the hooks for spectator mode. + * + * @license TS++ 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 + * 3 of the License, or (at your option) any later version. + * + * TS++ 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "spectator_hooks.h" + +#include "display.h" +#include "hooker.h" +#include "hooker_macros.h" +#include "house.h" +#include "housetype.h" +#include "session.h" +#include "spawner.h" +#include "mouse.h" + +/** + * A fake class for implementing new member functions which allow + * access to the "this" pointer of the intended class. + * + * @note: This must not contain a constructor or destructor. + * + * @note: All functions must not be virtual and must also be prefixed + * with "_" to prevent accidental virtualization. + */ +class HouseClassExt : public HouseClass +{ +public: + bool _Is_Spectator() const; + bool _Is_Coach() const; + bool _Is_Ally_Or_Spectator(const HouseClassExt* house) const; + void _Update_Radars(); + bool _Has_Player_Allies() const; +}; + + +/** + * Helper function that returns if the house is a spectator. + * + * @author: ZivDero + */ +bool HouseClassExt::_Is_Spectator() const +{ + if (Spawner::Active) + { + return Spawner::Get_Config()->Houses[Get_Heap_ID()].IsSpectator; + } + + return false; +} + + +/** + * Helper function that returns if the house is a coach. + * + * @author: ZivDero + */ +bool HouseClassExt::_Is_Coach() const +{ + if (Spawner::Active) + { + if (Spawner::Get_Config()->CoachMode) + { + return _Is_Spectator() && _Has_Player_Allies(); + } + } + + return false; +} + + +/** + * Helper function that returns if the house is allied to the other house, or is a spectator. + * + * @author: ZivDero + */ +bool HouseClassExt::_Is_Ally_Or_Spectator(const HouseClassExt* house) const +{ + bool is_ally = HouseClass::Is_Ally(house); + + if (Spawner::Active) + { + if (is_ally) + return is_ally; + + if (Spawner::Get_Config()->CoachMode) + { + return _Is_Coach() || house->_Is_Coach(); + } + + return _Is_Spectator() || house->_Is_Spectator(); + } + + return is_ally; +} + + +/** + * Helper function that returns if the player has any allies. + * + * @author: ZivDero + */ +bool HouseClassExt::_Has_Player_Allies() const +{ + const char* SPECIAL = "Special"; + + unsigned int allies = Allies; + + // Special is allied to everyone, so we need to exclude it from the allies list to get the real picture + int special_house_id = As_Pointer(HouseTypeClass::From_Name(SPECIAL))->Get_Heap_ID(); + allies &= ~(1 << special_house_id); + + return allies != 0; +} + + +/** + * Enable the radar for spectators. + * + * @author: ZivDero + */ +static bool spectator_radar_enabled = false; +void HouseClassExt::_Update_Radars() +{ + Update_Radars(); + + if (Spawner::Active) + { + if (this == PlayerPtr && !spectator_radar_enabled && _Is_Spectator()) + { + Map.IsRadarAvailable = true; + Map.RadarClass::Radar_Activate(1); + spectator_radar_enabled = true; + } + } +} + + +/** + * A fake class for implementing new member functions which allow + * access to the "this" pointer of the intended class. + * + * @note: This must not contain a constructor or destructor. + * + * @note: All functions must not be virtual and must also be prefixed + * with "_" to prevent accidental virtualization. + */ +class DisplayClassExt : public DisplayClass +{ +public: + void _Encroach_Shadow_Spectator(); + void _Encroach_Fog_Spectator(); +}; + + +/** + * Don't encroach shadow for spectators. + * + * @author: ZivDero + */ +void DisplayClassExt::_Encroach_Shadow_Spectator() +{ + if (Spawner::Active && reinterpret_cast(PlayerPtr)->_Is_Spectator() && !reinterpret_cast(PlayerPtr)->_Is_Coach()) + { + return; + } + + DisplayClass::Encroach_Shadow(); +} + + +/** + * Don't encroach fog for spectators. + * + * @author: ZivDero + */ +void DisplayClassExt::_Encroach_Fog_Spectator() +{ + if (Spawner::Active && reinterpret_cast(PlayerPtr)->_Is_Spectator() && !reinterpret_cast(PlayerPtr)->_Is_Coach()) + { + return; + } + + DisplayClass::Encroach_Fog(); +} + + +/** + * A fake class for implementing new member functions which allow + * access to the "this" pointer of the intended class. + * + * @note: This must not contain a constructor or destructor. + * + * @note: All functions must not be virtual and must also be prefixed + * with "_" to prevent accidental virtualization. + */ +class MapClassExt : public MapClass +{ +public: + void _Reveal_The_Map(); +}; + + +/** + * Don't reveal the map in coach mode. + * + * @author: ZivDero + */ +void MapClassExt::_Reveal_The_Map() +{ + if (Spawner::Active && Spawner::Get_Config()->CoachMode && reinterpret_cast(PlayerPtr)->_Is_Coach()) + { + return; + } + + MapClass::Reveal_The_Map(); +} + + +/** + * Don't count spectators as defeated players. + * + * @author: ZivDero + */ +DECLARE_PATCH(_HouseClass_MPlayer_Defeated_Dont_Count_Spectators) +{ + GET_REGISTER_STATIC(HouseClassExt*, hptr, eax); + _asm pushad + + if (Spawner::Active && hptr != PlayerPtr && Session.Type != GAME_SKIRMISH && hptr->_Is_Spectator()) + { + _asm popad + JMP(0x004BF74A); + } + + // Vanilla code + if (!hptr->IsDefeated && !hptr->Class->IsMultiplayPassive) + { + _asm popad + JMP_REG(ebp, 0x004BF730); + } + + _asm popad + JMP_REG(ebp, 0x004BF75D) +} + + +/** + * Don't process the radar for spectators. + * + * @author: ZivDero + */ +DECLARE_PATCH(_HouseClass_Radar_Outage_Spectators) +{ + GET_STACK_STATIC8(bool, tactical_availability, esp, 0x4); + GET_REGISTER_STATIC(HouseClassExt*, house, esi); + + if (!Spawner::Active || (house == PlayerPtr && !house->_Is_Spectator())) + { + Map.RadarClass::Toggle_Radar(tactical_availability); + } + + // Return + JMP(0x004C9693); +} + + +/** + * Reveal the map for spectators. + * + * @author: ZivDero + */ +DECLARE_PATCH(_RadarClass_Compute_Radar_Image) +{ + if (Spawner::Active) + { + spectator_radar_enabled = false; + if (reinterpret_cast(PlayerPtr)->_Is_Spectator() && !reinterpret_cast(PlayerPtr)->_Is_Coach() && PlayerPtr->IsDefeated) + { + Session.ObiWan = true; + Map.Reveal_The_Map(); + } + } + + // Function epilogue + _asm + { + pop edi + pop esi + pop ebp + add esp, 0x14 + ret + } +} + + +/** + * Main function for patching the hooks. + */ +void Spectator_Hooks() +{ + Patch_Call(0x00506D7B, &DisplayClassExt::_Encroach_Shadow_Spectator); + Patch_Call(0x00507291, &DisplayClassExt::_Encroach_Shadow_Spectator); + Patch_Call(0x00619AE9, &DisplayClassExt::_Encroach_Shadow_Spectator); + Patch_Call(0x0061B985, &DisplayClassExt::_Encroach_Shadow_Spectator); + Patch_Call(0x00506DFC, &DisplayClassExt::_Encroach_Fog_Spectator); + Patch_Call(0x00507309, &DisplayClassExt::_Encroach_Fog_Spectator); + Patch_Jump(0x004BF71B, &_HouseClass_MPlayer_Defeated_Dont_Count_Spectators); + Patch_Jump(0x004C9684, &_HouseClass_Radar_Outage_Spectators); + Patch_Call(0x0043852B, &HouseClassExt::_Is_Ally_Or_Spectator); + Patch_Call(0x00438540, &HouseClassExt::_Is_Ally_Or_Spectator); + Patch_Call(0x00633E85, &HouseClassExt::_Is_Ally_Or_Spectator); + Patch_Call(0x00633E9F, &HouseClassExt::_Is_Ally_Or_Spectator); + Patch_Call(0x0062C6CE, &HouseClassExt::_Is_Ally_Or_Spectator); + Patch_Call(0x0062CA26, &HouseClassExt::_Is_Ally_Or_Spectator); + Patch_Call(0x00428A23, &HouseClassExt::_Is_Ally_Or_Spectator); + Patch_Call(0x0047B0BB, &HouseClassExt::_Is_Ally_Or_Spectator); + Patch_Call(0x004BC608, &HouseClassExt::_Update_Radars); + Patch_Call(0x004BF5D6, &MapClassExt::_Reveal_The_Map); + Patch_Jump(0x005B9CFE, &_RadarClass_Compute_Radar_Image); +} diff --git a/src/cncnet/cncnet4/cncnet4_hooks.h b/src/spawner/modules/spectator_hooks.h similarity index 89% rename from src/cncnet/cncnet4/cncnet4_hooks.h rename to src/spawner/modules/spectator_hooks.h index 5dcc88fec..f1e910b29 100644 --- a/src/cncnet/cncnet4/cncnet4_hooks.h +++ b/src/spawner/modules/spectator_hooks.h @@ -4,11 +4,11 @@ * * @project Vinifera * - * @file CNCNET4_HOOKS.H + * @file SPECTATOR_HOOKS.H * - * @author CCHyper + * @author ZivDero * - * @brief Contains the hooks for the CnCNet4 system. + * @brief Contains the hooks for spectator mode. * * @license Vinifera is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,4 +28,4 @@ #pragma once -void CnCNet4_Hooks(); +void Spectator_Hooks(); diff --git a/src/spawner/modules/statistics_hooks.cpp b/src/spawner/modules/statistics_hooks.cpp new file mode 100644 index 000000000..0e7b2e85b --- /dev/null +++ b/src/spawner/modules/statistics_hooks.cpp @@ -0,0 +1,449 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file STATISTICS_HOOKS.CPP + * + * @author ZivDero + * + * @brief Contains the hooks for statistics collection. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "statistics_hooks.h" + +#include "hooker.h" +#include "hooker_macros.h" +#include "packet.h" +#include "scenario.h" +#include "session.h" +#include "spawner.h" +#include "field.h" +#include "house.h" +#include "housetype.h" +#include "tibsun_globals.h" + + +static bool Is_Statistics_Enabled() +{ + if (Spawner::Active) + { + return Spawner::Get_Config()->WriteStatistics + && Session.Type == GAME_IPX; + } + + // Vanilla condition + return Session.Type == GAME_INTERNET; +} + + +/** + * A fake class for implementing new member functions which allow + * access to the "this" pointer of the intended class. + * + * @note: This must not contain a constructor or destructor. + * + * @note: All functions must not be virtual and must also be prefixed + * with "_" to prevent accidental virtualization. + */ +class PacketClassExt : public PacketClass +{ +public: + char* _Create_Comms_Packet(int& size); + void _Add_Field_SCEN_ACCN_HASH(FieldClass* field); + void _Add_Field_Player_Data(FieldClass* field); +}; + + +/** + * Write statistics to a file for the client. + * + * @author: ZivDero + */ +char* PacketClassExt::_Create_Comms_Packet(int& size) +{ + char* result = Create_Comms_Packet(size); + + if (Is_Statistics_Enabled()) + { + CCFileClass stats_file("stats.dmp"); + if (stats_file.Open(FILE_ACCESS_WRITE)) + { + stats_file.Write(result, size); + stats_file.Close(); + } + + GameStatisticsPacketSent = true; + } + + return result; +} + + +/** + * Add some scenario-related fields to the statistics packet. + * + * @author: ZivDero + */ +void PacketClassExt::_Add_Field_SCEN_ACCN_HASH(FieldClass* field) +{ + if (Is_Statistics_Enabled()) + { + FieldClass scen("SCEN", Spawner::Get_Config()->UIMapName); + PacketClass::Add_Field(&scen); + + FieldClass accn("ACCN", PlayerPtr->IniName); + PacketClass::Add_Field(&accn); + + FieldClass hash("HASH", Spawner::Get_Config()->MapHash); + PacketClass::Add_Field(&hash); + + return; + } + + PacketClass::Add_Field(field); +} + + +/** + * Add some player-related fields to the statistics packet. + * + * @author: ZivDero + */ +void PacketClassExt::_Add_Field_Player_Data(FieldClass* field) +{ + // This is the global string "NAM?" + // The game replaces "?" with the player's ID before this call, + // so we can grab it from there. + // It should be also be the house ID. + static char*& field_player_handle = Make_Global(0x0070FCF4); + + if (Is_Statistics_Enabled()) + { + const char id = field_player_handle[3] - '0'; + + HouseClass* house = Houses[id]; + + if (house == PlayerPtr) + { + FieldClass myid("MYID", static_cast(id)); + PacketClass::Add_Field(&myid); + + FieldClass nkey("NKEY", static_cast(0)); + PacketClass::Add_Field(&nkey); + + FieldClass skey("SKEY", static_cast(0)); + PacketClass::Add_Field(&skey); + } + + static char field_player_allies[] = "ALY?"; + field_player_allies[3] = id; + FieldClass aly (field_player_allies, static_cast(house->Allies)); + PacketClass::Add_Field(&aly ); + + static char field_player_spawn[] = "BSP?"; + field_player_spawn[3] = id; + FieldClass bsp(field_player_spawn, static_cast(Spawner::Get_Config()->Houses[id].SpawnLocation)); + PacketClass::Add_Field(&bsp); + + static char field_player_spectator[] = "SPC?"; + field_player_spectator[3] = id; + FieldClass spc(field_player_spectator, static_cast(Spawner::Get_Config()->Houses[id].IsSpectator)); + PacketClass::Add_Field(&spc); + } + + PacketClass::Add_Field(field); +} + + +/** + * Numerous patches to enable statistics collection. + * + * @author: ZivDero + */ +DECLARE_PATCH(_BuildingClass_Captured_SendStatistics) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x0042F7A3); + } + + JMP(0x0042F7BB); +} + + +DECLARE_PATCH(_CellClass_Goodie_Check_SendStatistics) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x00457E83); + } + + JMP(0x00457E95); +} + + +DECLARE_PATCH(_HouseClass_Tracking_Add_SendStatistics1) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x004C2218); + } + + JMP(0x004C22FA); +} + + +DECLARE_PATCH(_HouseClass_Tracking_Add_SendStatistics2) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x004C2262); + } + + JMP(0x004C22FA); +} + + +DECLARE_PATCH(_HouseClass_Tracking_Add_SendStatistics3) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x004C22A8); + } + + JMP(0x004C22FA); +} + + +DECLARE_PATCH(_HouseClass_Tracking_Add_SendStatistics4) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x004C22EE); + } + + JMP(0x004C22FA); +} + + +DECLARE_PATCH(_TechnoClass_Record_The_Kill_SendStatistics1) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x00633893); + } + + JMP(0x006338B1); +} + + +DECLARE_PATCH(_TechnoClass_Record_The_Kill_SendStatistics2) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x006338FD); + } + + JMP(0x00633920); +} + + +DECLARE_PATCH(_TechnoClass_Record_The_Kill_SendStatistics3) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x00633965); + } + + JMP(0x00633983); +} + + +DECLARE_PATCH(_TechnoClass_Record_The_Kill_SendStatistics4) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x00633931); + } + + JMP(0x00633954); +} + + +DECLARE_PATCH(_Print_MP_Stats_Check) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x00463542); + } + + JMP(0x0046371F); +} + + +DECLARE_PATCH(_HouseClass_HouseClass_Create_Unit_Trackers) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x004BAC39); + } + + JMP(0x004BADB0); +} + + +DECLARE_PATCH(_Kick_Player_Now_SendStatistics) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x005B433C); + } + + JMP(0x005B439F); +} + + +DECLARE_PATCH(_Queue_AI_Multiplayer_SendStatistics) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x005B1EA0); + } + + JMP(0x005B1F21); +} + + +DECLARE_PATCH(_Main_Loop_SendStatistics1) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x00509229); + } + + JMP(0x0050924B); +} + + +DECLARE_PATCH(_Main_Loop_SendStatistics2) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x00509283); + } + + JMP(0x005092A5); +} + + +DECLARE_PATCH(_Execute_DoList_SendStatistics1) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x005B4FB9); + } + + JMP(0x005B500C); +} + + +DECLARE_PATCH(_Execute_DoList_SendStatistics2) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x005B4FDE); + } + + JMP(0x005B500C); +} + + +DECLARE_PATCH(_Main_Game_Start_Timer) +{ + if (Is_Statistics_Enabled()) + { + JMP(0x00462A2F); + } + + JMP(0x00462A46); +} + + +/** + * Don't send statistics for observers. + * + * @author: ZivDero + */ +DECLARE_PATCH(_Send_Statistics_Packet_Send_AI_Dont_Send_Observers) +{ + GET_REGISTER_STATIC(HouseClass*, house, eax); + _asm pushad + + if (Spawner::Active && Is_Statistics_Enabled()) + { + if (house->Class->IsMultiplayPassive || Spawner::Get_Config()->Houses[house->Get_Heap_ID()].IsSpectator) + { + _asm popad + JMP_REG(ecx, 0x006098EC); + } + } + else // Vanilla condition + { + if (!house->IsHuman) + { + _asm popad + JMP_REG(ecx, 0x006098EC); + } + } + + _asm popad + JMP_REG(ecx, 0x006098E6); +} + + +/** + * Main function for patching the hooks. + */ +void Statistics_Hooks() +{ + Patch_Call(0x0060A797, &PacketClassExt::_Create_Comms_Packet); + Patch_Jump(0x0042F799, &_BuildingClass_Captured_SendStatistics); + Patch_Jump(0x00457E7A, &_CellClass_Goodie_Check_SendStatistics); + Patch_Jump(0x004C220B, &_HouseClass_Tracking_Add_SendStatistics1); + Patch_Jump(0x004C2255, &_HouseClass_Tracking_Add_SendStatistics2); + Patch_Jump(0x004C229F, &_HouseClass_Tracking_Add_SendStatistics3); + Patch_Jump(0x004C22E5, &_HouseClass_Tracking_Add_SendStatistics4); + Patch_Jump(0x0063388A, &_TechnoClass_Record_The_Kill_SendStatistics1); + Patch_Jump(0x006338F4, &_TechnoClass_Record_The_Kill_SendStatistics2); + Patch_Jump(0x0063395C, &_TechnoClass_Record_The_Kill_SendStatistics3); + Patch_Jump(0x00633928, &_TechnoClass_Record_The_Kill_SendStatistics4); + Patch_Jump(0x0046353C, &_Print_MP_Stats_Check); + Patch_Jump(0x004BAC2C, &_HouseClass_HouseClass_Create_Unit_Trackers); + Patch_Jump(0x005B4333, &_Kick_Player_Now_SendStatistics); + Patch_Jump(0x005B1E94, &_Queue_AI_Multiplayer_SendStatistics); + Patch_Jump(0x00509220, &_Main_Loop_SendStatistics1); + Patch_Jump(0x0050927A, &_Main_Loop_SendStatistics2); + Patch_Jump(0x005B4FAE, &_Execute_DoList_SendStatistics1); + Patch_Jump(0x005B4FD3, &_Execute_DoList_SendStatistics2); + Patch_Jump(0x00462A26, &_Main_Game_Start_Timer); + Patch_Call(0x0060982A, &PacketClassExt::_Add_Field_SCEN_ACCN_HASH); + Patch_Call(0x00609DA6, &PacketClassExt::_Add_Field_Player_Data); + Patch_Jump(0x006098DC, &_Send_Statistics_Packet_Send_AI_Dont_Send_Observers); +} diff --git a/src/cncnet/cncnet5/cncnet5_globals.cpp b/src/spawner/modules/statistics_hooks.h similarity index 71% rename from src/cncnet/cncnet5/cncnet5_globals.cpp rename to src/spawner/modules/statistics_hooks.h index b30f9fe8a..95b8d8af5 100644 --- a/src/cncnet/cncnet5/cncnet5_globals.cpp +++ b/src/spawner/modules/statistics_hooks.h @@ -4,11 +4,11 @@ * * @project Vinifera * - * @file CNCNET_GLOBALS.CPP + * @file STATISTICS_HOOKS.H * - * @author CCHyper + * @author ZivDero * - * @brief Global values and types used for the CnCNet5 system. + * @brief Contains the hooks for statistics collection. * * @license Vinifera is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -25,20 +25,7 @@ * If not, see . * ******************************************************************************/ -#include "cncnet5_globals.h" +#pragma once -/** - * Has the CnCNet5 system been activated? - */ -bool CnCNet5::IsActive = false; - -/** - * Is the tunnel system active (set when tunnel information has been provided)? - */ -bool CnCNet5::IsTunnelActive = false; - -/** - * CnCNet5 UDP Tunnel info. - */ -CnCNet5::TunnelInfoStruct CnCNet5::TunnelInfo { -1, -1, -1, false }; +void Statistics_Hooks(); diff --git a/src/cncnet/cncnet5/cncnet5_wspudp.cpp b/src/spawner/net/cncnet5_wspudp.cpp similarity index 89% rename from src/cncnet/cncnet5/cncnet5_wspudp.cpp rename to src/spawner/net/cncnet5_wspudp.cpp index bc6873233..06a0d72ff 100644 --- a/src/cncnet/cncnet5/cncnet5_wspudp.cpp +++ b/src/spawner/net/cncnet5_wspudp.cpp @@ -36,7 +36,6 @@ */ CnCNet5UDPInterfaceClass::CnCNet5UDPInterfaceClass(unsigned short id, unsigned long ip, unsigned short port, bool port_hack) : UDPInterfaceClass(), - IsEnabled(false), AddressList(), TunnelID(id), TunnelIP(ip), @@ -55,14 +54,7 @@ CnCNet5UDPInterfaceClass::CnCNet5UDPInterfaceClass(unsigned short id, unsigned l */ LRESULT CnCNet5UDPInterfaceClass::Message_Handler(HWND hWnd, UINT uMsg, UINT wParam, LONG lParam) { - /** - * If the CnCNet interface has not been enabled, just use the standard UDP interface. - */ - if (!IsEnabled) { - return UDPInterfaceClass::Message_Handler(hWnd, uMsg, wParam, lParam); - } - - struct sockaddr_in addr; + sockaddr_in addr; int rc; int addr_len; WinsockBufferType *packet; @@ -94,9 +86,9 @@ LRESULT CnCNet5UDPInterfaceClass::Message_Handler(HWND hWnd, UINT uMsg, UINT wPa * Call the CnCNet tunnel Receive_From function to get the outstanding packet. */ addr_len = sizeof(addr); - rc = CnCNet5UDPInterfaceClass::Receive_From(Socket, (char*)ReceiveBuffer, sizeof(ReceiveBuffer), 0, (PSOCKADDR_IN)&addr, &addr_len); + rc = Receive_From(Socket, reinterpret_cast(ReceiveBuffer), sizeof(ReceiveBuffer), 0, &addr, &addr_len); if (rc == SOCKET_ERROR) { - DEBUG_WARNING("CnCNet5: Send_To returned %d!\n", WSAGetLastError()); + DEBUG_WARNING("CnCNet5: Receive_From returned %d!\n", WSAGetLastError()); Clear_Socket_Error(Socket); return 0; } @@ -143,15 +135,16 @@ LRESULT CnCNet5UDPInterfaceClass::Message_Handler(HWND hWnd, UINT uMsg, UINT wPa * Create a new buffer and store this packet in it. */ packet = Get_New_In_Buffer(); - packet->BufferLen = rc; - std::memcpy(packet->PacketData.Buffer, ReceiveBuffer, rc); + packet->BufferLen = rc - sizeof(packet->PacketData.CRC); + packet->PacketData.CRC = *reinterpret_cast(ReceiveBuffer); + std::memcpy(packet->PacketData.Buffer, ReceiveBuffer + sizeof(packet->PacketData.CRC), rc - sizeof(packet->PacketData.CRC)); if (!Passes_CRC_Check(packet)) { DEBUG_INFO("CnCNet5: Throwing away malformed packet!\n"); Delete_In_Buffer(packet); return 0; } std::memset(packet->Address, 0, sizeof (packet->Address)); - std::memcpy(packet->Address+4, &addr.sin_addr.s_addr, 4); + std::memcpy(packet->Address + 4, &addr.sin_addr.s_addr, 4); InBuffers.Add(packet); } return 0; @@ -185,7 +178,7 @@ LRESULT CnCNet5UDPInterfaceClass::Message_Handler(HWND hWnd, UINT uMsg, UINT wPa /** * (CnCNet) pull index. */ - int i = addr.sin_addr.s_addr - 1; + const int i = *reinterpret_cast(packet->Address + 4) - 1; /** * (CnCNet) validate index. @@ -207,8 +200,8 @@ LRESULT CnCNet5UDPInterfaceClass::Message_Handler(HWND hWnd, UINT uMsg, UINT wPa * at this time. In this case, we clear the socket error and just exit. Winsock will * send us another WRITE message when it is ready to receive more data. */ - rc = CnCNet5UDPInterfaceClass::Send_To(Socket, (const char *)&packet->PacketData, packet->BufferLen, 0, (PSOCKADDR_IN)&addr, sizeof (addr)); - if (rc == SOCKET_ERROR){ + rc = Send_To(Socket, reinterpret_cast(&packet->PacketData), packet->BufferLen + sizeof(packet->PacketData.CRC), 0, &addr, sizeof(addr)); + if (rc == SOCKET_ERROR) { if (WSAGetLastError() != WSAEWOULDBLOCK) { Clear_Socket_Error(Socket); return 0; @@ -241,7 +234,7 @@ int CnCNet5UDPInterfaceClass::Send_To(SOCKET s, const char *buf, int len, int fl /** * No processing if no tunnel. */ - if (TunnelPort == -1) { + if (TunnelPort == 0) { DEBUG_WARNING("CnCNet5: TunnelPort is invalid in Send_To!\n"); return sendto(s, buf, len, flags, (const sockaddr *)dest_addr, addrlen); } @@ -282,9 +275,9 @@ int CnCNet5UDPInterfaceClass::Receive_From(SOCKET s, char *buf, int len, int fla /** * No processing if no tunnel. */ - if (TunnelPort == -1) { + if (TunnelPort == 0) { DEBUG_WARNING("CnCNet5: TunnelPort is invalid in Recieve_From!\n"); - return recvfrom(s, buf, len, flags, (sockaddr *)src_addr, addrlen); + return recvfrom(s, buf, len, flags, reinterpret_cast(src_addr), addrlen); } #ifndef NDEBUG @@ -294,7 +287,7 @@ int CnCNet5UDPInterfaceClass::Receive_From(SOCKET s, char *buf, int len, int fla /** * Call recvfrom first to get the packet. */ - int ret = recvfrom(s, tempbuf, sizeof tempbuf, flags, (sockaddr *)src_addr, addrlen); + int ret = recvfrom(s, tempbuf, sizeof(tempbuf), flags, reinterpret_cast(src_addr), addrlen); /** * No processing if returning error or less than 5 bytes of data. diff --git a/src/cncnet/cncnet5/cncnet5_wspudp.h b/src/spawner/net/cncnet5_wspudp.h similarity index 87% rename from src/cncnet/cncnet5/cncnet5_wspudp.h rename to src/spawner/net/cncnet5_wspudp.h index d1e5349a8..38913038b 100644 --- a/src/cncnet/cncnet5/cncnet5_wspudp.h +++ b/src/spawner/net/cncnet5_wspudp.h @@ -31,11 +31,11 @@ #include "tibsun_defines.h" -typedef struct TunnelAddress +struct TunnelAddress { unsigned long IP; - unsigned long Port; -} TunnelAddress; + unsigned short Port; +}; /** @@ -49,7 +49,7 @@ class CnCNet5UDPInterfaceClass : public UDPInterfaceClass { public: CnCNet5UDPInterfaceClass(unsigned short id, unsigned long ip, unsigned short port, bool port_hack = false); - virtual ~CnCNet5UDPInterfaceClass() {} + virtual ~CnCNet5UDPInterfaceClass() override = default; virtual LRESULT Message_Handler(HWND hWnd, UINT uMsg, UINT wParam, LONG lParam) override; @@ -58,13 +58,6 @@ class CnCNet5UDPInterfaceClass : public UDPInterfaceClass int Receive_From(SOCKET s, char *buf, int len, int flags, sockaddr_in *src_addr, int *addrlen); public: - /** - * Should be CnCNet5 tunnel system interface be used over WinSock? - * - * @note: This should manually be set after instantiating the class. - */ - bool IsEnabled; - TunnelAddress AddressList[MAX_PLAYERS]; unsigned short TunnelID; diff --git a/src/spawner/net/latencylevel.cpp b/src/spawner/net/latencylevel.cpp new file mode 100644 index 000000000..d653526b9 --- /dev/null +++ b/src/spawner/net/latencylevel.cpp @@ -0,0 +1,141 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file LATENCYLEVEL.CPP + * + * @author Belonit, ZivDero + * + * @brief Protocol zero latency level class. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "latencylevel.h" + +#include "colorscheme.h" +#include "protocolzero.h" +#include "debughandler.h" +#include "house.h" +#include "session.h" +#include "rules.h" + + +LatencyLevelEnum LatencyLevel::CurentLatencyLevel = LATENCY_LEVEL_INITIAL; +unsigned char LatencyLevel::NewFrameSendRate = 3; + + +/** + * Sets the desired latency level. + * + * @author: Belonit + */ +void LatencyLevel::Apply(LatencyLevelEnum new_latency_level) +{ + if (new_latency_level > LATENCY_LEVEL_MAX) + new_latency_level = LATENCY_LEVEL_MAX; + + auto max_latency_level = static_cast(ProtocolZero::MaxLatencyLevel); + if (new_latency_level > max_latency_level) + new_latency_level = max_latency_level; + + if (new_latency_level <= CurentLatencyLevel) + return; + + DEBUG_INFO("[Spawner] Player %ls, LatencyMode (%d, %d) Frame = %d\n" + , PlayerPtr->IniName + , new_latency_level + , CurentLatencyLevel + , Frame + ); + + CurentLatencyLevel = new_latency_level; + NewFrameSendRate = static_cast(new_latency_level); + Session.PrecalcDesiredFrameRate = 60; + Session.PrecalcMaxAhead = Get_Max_Ahead(new_latency_level); + Session.Messages.Add_Message(nullptr, 0, Get_Latency_Message(new_latency_level), ColorScheme::From_Name("White"), TPF_USE_GRAD_PAL | TPF_FULLSHADOW | TPF_6PT_GRAD, static_cast(Rule->MessageDelay * TICKS_PER_MINUTE / 2)); +} + + +/** + * Gets the max ahead for the given latency level. + * + * @author: Belonit + */ +unsigned int LatencyLevel::Get_Max_Ahead(LatencyLevelEnum latency_level) +{ + const int maxAhead[] = + { + /* 0 */ 1, + + /* 1 */ 4, + /* 2 */ 6, + /* 3 */ 12, + /* 4 */ 16, + /* 5 */ 20, + /* 6 */ 24, + /* 7 */ 28, + /* 8 */ 32, + /* 9 */ 36 + }; + + return maxAhead[latency_level]; +} + + +/** + * Gets the chat message for the given latency level. + * + * @author: Belonit + */ +const char* LatencyLevel::Get_Latency_Message(LatencyLevelEnum latency_level) +{ + const char* message[] = + { + /* 0 */ "CnCNet: Latency mode set to: 0 - Initial", // Players should never see this, if they do, then it's a bug + + /* 1 */ "CnCNet: Latency mode set to: 1 - Best", + /* 2 */ "CnCNet: Latency mode set to: 2 - Super", + /* 3 */ "CnCNet: Latency mode set to: 3 - Excellent", + /* 4 */ "CnCNet: Latency mode set to: 4 - Very Good", + /* 5 */ "CnCNet: Latency mode set to: 5 - Good", + /* 6 */ "CnCNet: Latency mode set to: 6 - Good", + /* 7 */ "CnCNet: Latency mode set to: 7 - Default", + /* 8 */ "CnCNet: Latency mode set to: 8 - Default", + /* 9 */ "CnCNet: Latency mode set to: 9 - Default", + }; + + return message[latency_level]; +} + + +/** + * Gets the latency level for the given response time. + * + * @author: Belonit + */ +LatencyLevelEnum LatencyLevel::From_Response_Time(unsigned int response_time) +{ + for (char i = LATENCY_LEVEL_1; i < LATENCY_LEVEL_MAX; i++) + { + if (response_time <= Get_Max_Ahead(static_cast(i))) + return static_cast(i); + } + + return LATENCY_LEVEL_MAX; +} diff --git a/src/cncnet/cncnet4/cncnet4_globals.h b/src/spawner/net/latencylevel.h similarity index 50% rename from src/cncnet/cncnet4/cncnet4_globals.h rename to src/spawner/net/latencylevel.h index 098937913..4f4ac7e85 100644 --- a/src/cncnet/cncnet4/cncnet4_globals.h +++ b/src/spawner/net/latencylevel.h @@ -4,11 +4,11 @@ * * @project Vinifera * - * @file CNCNET4_GLOBALS.H + * @file LATENCYLEVEL.H * - * @author CCHyper + * @author Belonit, ZivDero * - * @brief CnCNet4 global values. + * @brief Protocol zero latency level class. * * @license Vinifera is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -27,30 +27,42 @@ ******************************************************************************/ #pragma once -#include -#include +enum LatencyLevelEnum : unsigned char +{ + LATENCY_LEVEL_INITIAL = 0, -namespace CnCNet4 { + LATENCY_LEVEL_1 = 1, + LATENCY_LEVEL_2 = 2, + LATENCY_LEVEL_3 = 3, + LATENCY_LEVEL_4 = 4, + LATENCY_LEVEL_5 = 5, + LATENCY_LEVEL_6 = 6, + LATENCY_LEVEL_7 = 7, + LATENCY_LEVEL_8 = 8, + LATENCY_LEVEL_9 = 9, -extern bool IsEnabled; + LATENCY_LEVEL_MAX = LATENCY_LEVEL_9, + LATENCY_SIZE = 1 + LATENCY_LEVEL_MAX +}; -extern char Host[256]; -extern unsigned Port; -extern bool Peer2Peer; -extern bool IsDedicated; -extern bool UseUDP; -extern struct sockaddr_in Server; - -}; // namespace CnCNet4 +/** + * LatencyLevel + * + * This class is contains methods for working with Protocol 0 latency levels. + */ +class LatencyLevel +{ +public: + LatencyLevel() = delete; + static LatencyLevelEnum CurentLatencyLevel; + static unsigned char NewFrameSendRate; -int __stdcall bind_intercept(SOCKET s, const struct sockaddr *name, int namelen); -int __stdcall closesocket_intercept(SOCKET s); -int __stdcall getsockname_intercept(SOCKET s, struct sockaddr *name, int *namelen); -int __stdcall getsockopt_intercept(SOCKET s, int level, int optname, char *optval, int *optlen); -int __stdcall recvfrom_intercept(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen); -int __stdcall sendto_intercept(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen); -int __stdcall setsockopt_intercept(SOCKET s, int level, int optname, const char *optval, int optlen); -SOCKET __stdcall socket_intercept(int af, int type, int protocol); + static void Apply(LatencyLevelEnum new_latency_level); + static void Apply(unsigned char new_latency_level) { Apply(static_cast(new_latency_level)); } + static unsigned int Get_Max_Ahead(LatencyLevelEnum latency_level); + static const char* Get_Latency_Message(LatencyLevelEnum latency_level); + static LatencyLevelEnum From_Response_Time(unsigned int response_time); +}; diff --git a/src/spawner/net/protocolzero.cpp b/src/spawner/net/protocolzero.cpp new file mode 100644 index 000000000..e223d3abf --- /dev/null +++ b/src/spawner/net/protocolzero.cpp @@ -0,0 +1,156 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file PROTOCOLZERO.CPP + * + * @author Belonit, ZivDero + * + * @brief Protocol zero. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "protocolzero.h" + +#include "latencylevel.h" +#include "spawner.h" +#include "viniferaevent/viniferaevent.h" +#include "house.h" +#include "session.h" +#include "ipxmgr.h" + +#include "debughandler.h" + + +bool ProtocolZero::Enable = false; +bool ProtocolZero::GetRealMaxAhead = false; +unsigned int ProtocolZero::WorstMaxAhead = 24; +unsigned char ProtocolZero::MaxLatencyLevel = 0xff; + + +/** + * Sends a Response Time event. + * + * @author: Belonit + */ +void ProtocolZero::Send_Response_Time() +{ + if (Enable == false || Session.Singleplayer_Game()) + return; + + static int NextSendFrame = 6 * SendResponseTimeInterval; + + /** + * It is not yet time to send a Response Time event. + */ + if (Frame <= NextSendFrame) + return; + + /** + * IPXManagerClass::Response_Time is patched to return ProtocolZero::MaxAhead, + * so to get the real MaxAhead we set this bool to true just for this call. + */ + GetRealMaxAhead = true; + const unsigned int ipxResponseTime = Ipx.Response_Time(); + GetRealMaxAhead = false; + + /** + * Create the event. + */ + ViniferaEventClass event; + event.Type = VEVENT_RESPONSE_TIME_2; + event.ID = PlayerPtr->Get_Heap_ID(); + event.Frame = Frame + Session.MaxAhead; + event.Data.ResponseTime2.MaxAhead = static_cast(ipxResponseTime + 1); + event.Data.ResponseTime2.LatencyLevel = LatencyLevel::From_Response_Time(ipxResponseTime); + + /** + * Send it! + */ + if (OutList.Add(event.As_Event())) + { + NextSendFrame = Frame + SendResponseTimeInterval; + DEBUG_INFO("[Spawner] Player %d sending response time of %u, LatencyMode = %d, Frame = %d\n" + , event.ID + , event.Data.ResponseTime2.MaxAhead + , event.Data.ResponseTime2.LatencyLevel + , Frame + ); + } + else + { + NextSendFrame++; + } +} + + +/** + * Executes a Response Time event. + * + * @author: Belonit + */ +void ProtocolZero::Handle_Response_Time(ViniferaEventClass* event) +{ + if (Enable == false || Session.Singleplayer_Game()) + return; + + if (event->Data.ResponseTime2.MaxAhead == 0) + { + DEBUG_INFO("[Spawner] Returning because event->MaxAhead == 0\n"); + return; + } + + static unsigned int PlayerMaxAheads[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + static unsigned char PlayerLatencyMode[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + static unsigned int PlayerLastTimingFrame[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + /** + * Save the info we got from the event. + */ + PlayerMaxAheads[event->ID] = event->Data.ResponseTime2.MaxAhead; + PlayerLatencyMode[event->ID] = event->Data.ResponseTime2.LatencyLevel; + PlayerLastTimingFrame[event->ID] = event->Frame; + + /** + * Now loop all the players and find the worst one latency-wise. + */ + unsigned char latency_mode = 0; + unsigned int max_ahead = 0; + + for (size_t i = 0; i < std::size(PlayerMaxAheads); i++) + { + if (PlayerLastTimingFrame[i] + SendResponseTimeInterval * 4 < Frame) + { + PlayerMaxAheads[i] = 0; + PlayerLatencyMode[i] = 0; + } + else + { + max_ahead = PlayerMaxAheads[i] > max_ahead ? PlayerMaxAheads[i] : max_ahead; + if (PlayerLatencyMode[i] > latency_mode) + latency_mode = PlayerLatencyMode[i]; + } + } + + /** + * The worst determines the settings for all the players. + */ + WorstMaxAhead = max_ahead; + LatencyLevel::Apply(latency_mode); +} diff --git a/src/cncnet/cncnet4/cncnet4_globals.cpp b/src/spawner/net/protocolzero.h similarity index 65% rename from src/cncnet/cncnet4/cncnet4_globals.cpp rename to src/spawner/net/protocolzero.h index 3dca2d98d..ab5559326 100644 --- a/src/cncnet/cncnet4/cncnet4_globals.cpp +++ b/src/spawner/net/protocolzero.h @@ -4,11 +4,11 @@ * * @project Vinifera * - * @file CNCNET4_GLOBALS.CPP + * @file PROTOCOLZERO.H * - * @author CCHyper + * @author Belonit, ZivDero * - * @brief CnCNet4 global values. + * @brief Protocol zero. * * @license Vinifera is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -25,31 +25,27 @@ * If not, see . * ******************************************************************************/ -#include "cncnet4_globals.h" -#include "cncnet4.h" +#pragma once +class ViniferaEventClass; -/** - * Is the CnCNet4 interface active? - */ -bool CnCNet4::IsEnabled = false; - -/** - * The host name (Must be running a instance of the dedicated server). - */ -char CnCNet4::Host[256] = { "server.cncnet.org" }; -unsigned CnCNet4::Port = 9001; /** - * Clients connect to each other rather than the server? + * ProtocolZero + * + * This class is contains methods and the state of Protocol 0. */ -bool CnCNet4::Peer2Peer = false; - -bool CnCNet4::IsDedicated = false; +class ProtocolZero +{ +private: + static constexpr int SendResponseTimeInterval = 30; -/** - * Use the UDP interface instead of IPX? - */ -bool CnCNet4::UseUDP = true; +public: + static bool Enable; + static bool GetRealMaxAhead; + static unsigned char MaxLatencyLevel; + static unsigned int WorstMaxAhead; -struct sockaddr_in CnCNet4::Server; + static void Send_Response_Time(); + static void Handle_Response_Time(ViniferaEventClass* event); +}; diff --git a/src/spawner/net/protocolzero_hooks.cpp b/src/spawner/net/protocolzero_hooks.cpp new file mode 100644 index 000000000..6e3dfbdca --- /dev/null +++ b/src/spawner/net/protocolzero_hooks.cpp @@ -0,0 +1,340 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file PROTOCOLZERO_HOOKS.CPP + * + * @author ZivDero + * + * @brief Contains the hooks for protocol zero. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "protocolzero_hooks.h" + +#include "hooker.h" +#include "hooker_macros.h" +#include "latencylevel.h" +#include "protocolzero.h" +#include "tibsun_globals.h" +#include "session.h" +#include "viniferaevent/viniferaevent.h" +#include "ipxmgr.h" +#include "scenario.h" +#include "spawner.h" + +/** + * A fake class for implementing new member functions which allow + * access to the "this" pointer of the intended class. + * + * @note: This must not contain a constructor or destructor. + * + * @note: All functions must not be virtual and must also be prefixed + * with "_" to prevent accidental virtualization. + */ +class IPXManagerClassExt : public IPXManagerClass +{ +public: + void _Set_Timing(unsigned long retrydelta, unsigned long maxretries, unsigned long timeout, bool global = true); + unsigned long _Response_Time(); +}; + + +/** + * Patch to log network parameters. + * + * @author: ZivDero + */ +void IPXManagerClassExt::_Set_Timing(unsigned long retrydelta, unsigned long maxretries, unsigned long timeout, bool global) +{ + if (ProtocolZero::Enable) { + DEBUG_INFO("[Spawner] NewRetryDelta = %d, NewRetryTimeout = %d, FrameSendRate = %d, CurentLatencyLevel = %d\n" + , retrydelta + , maxretries + , Session.FrameSendRate + , LatencyLevel::CurentLatencyLevel + ); + } + + /** + * Vanilla function. + */ + DEBUG_INFO("RetryDelta = %d\n", retrydelta); + DEBUG_INFO("MaxAhead is %d\n", Session.MaxAhead); + + RetryDelta = retrydelta; + MaxRetries = maxretries; + Timeout = timeout; + + if (global) { + Set_External_Timing(RetryDelta, MaxRetries, Timeout); + } + + for (int i = 0; i < NumConnections; i++) { + Connection[i]->Set_Retry_Delta(RetryDelta); + Connection[i]->Set_Max_Retries(MaxRetries); + Connection[i]->Set_TimeOut(Timeout); + } +} + + +/** + * Patch IPXManagerClass to return our MaxAhead when Protocol 0 is active. + * + * @author: ZivDero + */ +unsigned long IPXManagerClassExt::_Response_Time() +{ + if (ProtocolZero::Enable && !ProtocolZero::GetRealMaxAhead) { + return ProtocolZero::WorstMaxAhead; + } + + // Vanilla function + unsigned long maxresp = 0; + + for (int i = 0; i < NumConnections; i++) { + unsigned long resp = Connection[i]->Queue->Avg_Response_Time(); + if (resp > maxresp) { + maxresp = resp; + } + } + + return maxresp; +} + + +/** + * A fake class for implementing new member functions which allow + * access to the "this" pointer of the intended class. + * + * @note: This must not contain a constructor or destructor. + * + * @note: All functions must not be virtual and must also be prefixed + * with "_" to prevent accidental virtualization. + */ +class MessageListClassExt : public MessageListClass +{ +public: + bool _Manage(); +}; + + +/** + * Convenient patch in Main_Loop to send a Response Time event. + * + * @author: ZivDero + */ +bool MessageListClassExt::_Manage() +{ + if (ProtocolZero::Enable) + ProtocolZero::Send_Response_Time(); + + return MessageListClass::Manage(); +} + + +/** + * A fake class for implementing new member functions which allow + * access to the "this" pointer of the intended class. + * + * @note: This must not contain a constructor or destructor. + * + * @note: All functions must not be virtual and must also be prefixed + * with "_" to prevent accidental virtualization. + */ +class EventClassExt : public EventClass +{ +public: + void _Execute_Timing(); +}; + + +/** + * Executes the adjusted Timing event. + * + * @author: ZivDero + */ +void EventClassExt::_Execute_Timing() +{ + if (!ProtocolZero::Enable) + Data.Timing.MaxAhead -= Scen->SpecialFlags.IsFogOfWar ? 10 : 0; + + if (Data.Timing.MaxAhead > Session.MaxAhead || Data.Timing.FrameSendRate > Session.FrameSendRate) + { + NewMaxAheadFrame1 = Frame; + NewMaxAheadFrame2 = Data.Timing.FrameSendRate * ((Data.Timing.FrameSendRate + Data.Timing.MaxAhead + Frame - 1) / Data.Timing.FrameSendRate); + } + else + { + NewMaxAheadFrame1 = 0; + NewMaxAheadFrame2 = 0; + } + + Session.DesiredFrameRate = Data.Timing.DesiredFrameRate; + Session.MaxAhead = Data.Timing.MaxAhead; + if (Session.MaxAhead > Session.MaxMaxAhead) + Session.MaxMaxAhead = Session.MaxAhead; + Session.FrameSendRate = Data.Timing.FrameSendRate; +} + + +/** + * Patch executing the Timing event, taking Protocol 0 into consideration. + * + * @author: ZivDero + */ +DECLARE_PATCH(_ProtocolZero_EventClass_Execute) +{ + GET_REGISTER_STATIC(EventClassExt*, e, esi); + + e->_Execute_Timing(); + + JMP(0x004950AD); +} + + +/** + * Skip checking MySent, if Protocol 0 is active. + * + * @author: ZivDero + */ +static short& MySent = Make_Global(0x008099F0); +DECLARE_PATCH(_ProtocolZero_Queue_AI_Multiplayer_1) +{ + if (ProtocolZero::Enable || MySent >= 5) + { + JMP(0x005B1A3B); + } + + JMP(0x005B1C4C); +} + + +/** + * Adds the adjusted Timing event. + * + * @author: ZivDero + */ +static void Add_Timing_Event() +{ + DEBUG_INFO("[Spawner] Sending precalculated network timings on frame %d\n", Frame); + + EventClass ev; + ev.Type = EVENT_TIMING; + ev.Data.Timing.DesiredFrameRate = Session.PrecalcDesiredFrameRate; + ev.Data.Timing.MaxAhead = Session.PrecalcMaxAhead; + ev.Data.Timing.FrameSendRate = ProtocolZero::Enable ? LatencyLevel::NewFrameSendRate : + Session.PrecalcDesiredFrameRate > 30 ? 10 : 5; + + OutList.Add(ev); + Session.PrecalcMaxAhead = 0; + Session.PrecalcDesiredFrameRate = 0; +} + + +/** + * Patch adding the Timing event, taking Protocol 0 into consideration. + * + * @author: ZivDero + */ +DECLARE_PATCH(_ProtocolZero_Queue_AI_Multiplayer_2) +{ + Add_Timing_Event(); + JMP(0x005B1C4C); +} + + +/** + * Adds the adjusted Timing event. + * + * @author: ZivDero + */ +static void Add_Timing_Event_2(int max_ahead) +{ + EventClass ev; + ev.Type = EVENT_TIMING; + ev.Data.Timing.DesiredFrameRate = Session.DesiredFrameRate; + ev.Data.Timing.MaxAhead = ProtocolZero::Enable ? Session.MaxAhead : (max_ahead + Scen->SpecialFlags.IsFogOfWar ? 10 : 0); + ev.Data.Timing.FrameSendRate = Session.FrameSendRate; + + OutList.Add(ev); +} + + +/** + * Patch adding the Timing event, taking Protocol 0 into consideration. + * + * @author: ZivDero + */ +DECLARE_PATCH(_ProtocolZero_Queue_AI_Multiplayer_3) +{ + GET_REGISTER_STATIC(int, max_ahead, edi); + _asm push esi + + Add_Timing_Event_2(max_ahead); + + _asm pop esi + JMP(0x005B1BB9); +} + + +/** + * If Protocol 0 is enabled, allow some types of packets to come in late. + * + * @author: ZivDero + */ +DECLARE_PATCH(_ProtocolZero_ExecuteDoList) +{ + GET_REGISTER_STATIC(EventClass*, event, esi) + + if (ProtocolZero::Enable) + { + if (event->Type == EVENT_EMPTY) + goto continue_execution; + + if (event->Type == EVENT_PROCESS_TIME) + goto continue_execution; + + if (event->Type == VEVENT_RESPONSE_TIME_2) + goto continue_execution; + } + + _asm mov eax, Session + _asm mov eax, [eax] + JMP_REG(ecx, 0x005B4EAA); + + continue_execution: + JMP(0x005B4EB7); +} + + +/** + * Main function for patching the hooks. + */ +void ProtocolZero_Hooks() +{ + Patch_Call(0x005091A5, &MessageListClassExt::_Manage); + Patch_Jump(0x005B1A2D, &_ProtocolZero_Queue_AI_Multiplayer_1); + Patch_Jump(0x005B1BF1, &_ProtocolZero_Queue_AI_Multiplayer_2); + Patch_Jump(0x005B1B7A, &_ProtocolZero_Queue_AI_Multiplayer_3); + Patch_Jump(0x00495013, &_ProtocolZero_EventClass_Execute); + Patch_Jump(0x005B4EA5, &_ProtocolZero_ExecuteDoList); + Patch_Jump(0x004F05B0, &IPXManagerClassExt::_Set_Timing); + Patch_Jump(0x004F0F00, &IPXManagerClassExt::_Response_Time); +} diff --git a/src/spawner/net/protocolzero_hooks.h b/src/spawner/net/protocolzero_hooks.h new file mode 100644 index 000000000..d7fe78549 --- /dev/null +++ b/src/spawner/net/protocolzero_hooks.h @@ -0,0 +1,31 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file PROTOCOLZERO_HOOKS.H + * + * @author ZivDero + * + * @brief Contains the hooks for protocol zero. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ +#pragma once + + +void ProtocolZero_Hooks(); diff --git a/src/spawner/spawner.cpp b/src/spawner/spawner.cpp new file mode 100644 index 000000000..f1e2fdd7b --- /dev/null +++ b/src/spawner/spawner.cpp @@ -0,0 +1,530 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file SPAWNER.CPP + * + * @author Belonit, ZivDero + * + * @brief Multiplayer spawner class. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "spawner.h" +#include "protocolzero.h" +#include "latencylevel.h" + +#include "options.h" +#include "house.h" +#include "ipxmgr.h" +#include "loadoptions.h" +#include "scenario.h" +#include + +#include "addon.h" +#include "wspudp.h" +#include "wwmouse.h" +#include "ccini.h" +#include "cncnet5_wspudp.h" +#include "debughandler.h" +#include "extension_globals.h" +#include "gscreen.h" +#include "housetype.h" +#include "language.h" +#include "mouse.h" +#include "ownrdraw.h" +#include "saveload.h" +#include "tab.h" +#include "WinUser.h" +#include "sessionext.h" +#include "tibsun_functions.h" +#include "rules.h" + + +bool Spawner::Active = false; +std::unique_ptr Spawner::Config = nullptr; + + +/** + * Initializes the Spawner. + * + * @author: ZivDero + */ +void Spawner::Init() +{ + Config = std::make_unique(); + + CCFileClass spawn_file("SPAWN.INI"); + CCINIClass spawn_ini; + + if (spawn_file.Is_Available()) { + + spawn_ini.Load(spawn_file, false); + Config->Read_INI(spawn_ini); + + } + else { + DEBUG_FATAL("SPAWN.INI not found!\n"); + } +} + + +/** + * Starts the game. + * + * @author: ZivDero + */ +bool Spawner::Start_Game() +{ + if (Active) + return false; + + Active = true; + GameActive = true; + + Init_UI(); + Read_Houses_And_Sides(); + + const bool result = Start_Scenario(Config->ScenarioName); + + Prepare_Screen(); + + return result; +} + + +/** + * Starts a new scenario. + * + * @author: ZivDero + */ +bool Spawner::Start_Scenario(const char* scenario_name) +{ + /** + * Can't read an unnamed file, bail. + */ + if (scenario_name[0] == 0 && !Config->LoadSaveGame) + { + DEBUG_INFO("[Spawner] Failed to read scenario [%s]\n", scenario_name); + MessageBox(MainWindow, Text_String(TXT_UNABLE_READ_SCENARIO), "Vinifera", MB_OK); + + return false; + } + + /** + * Turn Firestorm on, if requested. + */ + Disable_Addon(ADDON_ANY); + if (Config->Firestorm) + { + Enable_Addon(ADDON_FIRESTORM); + Set_Required_Addon(ADDON_FIRESTORM); + } + + /** + * Configure session options. + */ + strcpy_s(Session.ScenarioFileName, 0x200, scenario_name); + Session.Options.ScenarioIndex = -1; + Session.Options.Bases = Config->Bases; + Session.Options.Credits = Config->Credits; + Session.Options.BridgeDestruction = Config->BridgeDestroy; + Session.Options.Goodies = Config->Crates; + Session.Options.ShortGame = Config->ShortGame; + SessionExtension->ExtOptions.IsBuildOffAlly = Config->BuildOffAlly; + Session.Options.GameSpeed = Config->GameSpeed; + Session.Options.CrapEngineers = Config->MultiEngineer; + Session.Options.UnitCount = Config->UnitCount; + Session.Options.AIPlayers = Config->AIPlayers; + Session.Options.AIDifficulty = Config->AIDifficulty; + Session.Options.AlliesAllowed = Config->AlliesAllowed; + Session.Options.HarvesterTruce = Config->HarvesterTruce; + // Session.Options.CaptureTheFlag + Session.Options.FogOfWar = Config->FogOfWar; + Session.Options.RedeployMCV = Config->MCVRedeploy; + std::strcpy(Session.Options.ScenarioDescription, Config->UIMapName); + Session.ColorIdx = static_cast(Config->Players[0].Color); + Session.NumPlayers = Config->HumanPlayers; + + Seed = Config->Seed; + BuildLevel = Config->TechLevel; + Options.GameSpeed = Config->GameSpeed; + + // Inverted for now as the sidebar hack until we reimplement loading + Session.IsGDI = true;// HouseTypes[Config->Players[0].House]->Get_Heap_ID(); + //Session.IsGDI = HouseTypes[Config->Players[0].House]->Side != SIDE_NOD; + DEBUG_INFO("[Spawner] Session.IsGDI = %d\n", Session.IsGDI); + + /** + * Create the player node for the local player. + */ + const auto nodename = new NodeNameType(); + Session.Players.Add(nodename); + + std::strcpy(nodename->Name, Config->Players[0].Name); + nodename->Player.House = static_cast(Config->Players[0].House); + nodename->Player.Color = static_cast(Config->Players[0].Color); + nodename->Player.ProcessTime = -1; + nodename->Game.LastTime = 1; + + /** + * Set session type. + */ + if (Config->IsCampaign) + Session.Type = GAME_NORMAL; + else if (Session.NumPlayers > 1) + Session.Type = GAME_INTERNET; // HACK: will be set to GAME_IPX later + else + Session.Type = GAME_SKIRMISH; + + + Init_Random(); + + /** + * Start the scenario. + */ + if (Session.Type == GAME_NORMAL) + { + Session.Options.Goodies = true; + + const bool result = Config->LoadSaveGame ? + Load_Game(Config->SaveGameName) : ::Start_Scenario(scenario_name, false, static_cast(Config->CampaignID)); + + return result; + } + else if (Session.Type == GAME_SKIRMISH) + { + const bool result = Config->LoadSaveGame ? + Load_Game(Config->SaveGameName) : ::Start_Scenario(scenario_name, false, CAMPAIGN_NONE); + + return result; + } + else + { + Init_Network(); + + bool result = Config->LoadSaveGame ? + Load_Game(Config->SaveGameName) : ::Start_Scenario(scenario_name, false, CAMPAIGN_NONE); + + if (!result) + return false; + + Session.Type = GAME_IPX; + + if (Config->LoadSaveGame && !Reconcile_Players()) + return false; + + if (!Session.Create_Connections()) + return false; + + return true; + } +} + + +/** + * Loads a saved game. + * + * @author: ZivDero + */ +bool Spawner::Load_Game(const char* file_name) +{ + if (!file_name[0] || !::Load_Game(file_name)) + { + DEBUG_INFO("[Spawner] Failed to load savegame [%s]\n", file_name); + MessageBox(MainWindow, Text_String(TXT_ERROR_LOADING_GAME), "Vinifera", MB_OK); + + return false; + } + + return true; +} + + +/** + * Initializes everything necessary for an MP game. + * + * @author: ZivDero + */ +void Spawner::Init_Network() +{ + const unsigned short tunnel_id = htons(Config->TunnelId); + const unsigned long tunnel_ip = inet_addr(Config->TunnelIp); + const unsigned short tunnel_port = htons(Config->TunnelPort); + + /** + * Create the UDP interface. + * This needs to happen before we set up player nodes, + * because it contains player connection data. + */ + const auto udp_interface = new CnCNet5UDPInterfaceClass(tunnel_id, tunnel_ip, tunnel_port, true); + PacketTransport = udp_interface; + + PlanetWestwoodPortNumber = tunnel_port ? 0 : Config->ListenPort; + + /** + * Set up the player nodes. + */ + const char max_players = std::size(Config->Players); + for (char player_index = 1; player_index < max_players; player_index++) + { + const auto player = &Config->Players[player_index]; + if (!player->IsHuman) + continue; + + const auto nodename = new NodeNameType(); + Session.Players.Add(nodename); + + std::strcpy(nodename->Name, player->Name); + nodename->Player.House = static_cast(player->House); + nodename->Player.Color = static_cast(player->Color); + nodename->Player.ProcessTime = -1; + nodename->Game.LastTime = 1; + + std::memset(&nodename->Address, 0, sizeof(nodename->Address)); + std::memcpy(&nodename->Address.NetworkNumber, &player_index, sizeof(player_index)); + std::memcpy(&nodename->Address.NodeAddress, &player_index, sizeof(player_index)); + + const auto ip = inet_addr(player->Ip); + const auto port = htons(player->Port); + udp_interface->AddressList[player_index - 1].IP = ip; + udp_interface->AddressList[player_index - 1].Port = port; + if (port != Config->ListenPort) + udp_interface->PortHack = false; + } + + /** + * Now set up the rest of the network stuff. + */ + PacketTransport->Init(); + PacketTransport->Open_Socket(0); + PacketTransport->Start_Listening(); + PacketTransport->Discard_In_Buffers(); + PacketTransport->Discard_Out_Buffers(); + Ipx.Set_Timing(60, -1, 600, true); + + PlanetWestwoodStartTime = time(nullptr); + GameFSSKU = 0x1C00; + GameSKU = 0x1D00; + + /** + * Set up protocol stuff. + */ + ProtocolZero::Enable = (Config->Protocol == 0); + if (ProtocolZero::Enable) + { + Session.FrameSendRate = 2; + Session.PrecalcMaxAhead = Config->PreCalcMaxAhead; + ProtocolZero::MaxLatencyLevel = std::clamp( + Config->MaxLatencyLevel, + static_cast(LATENCY_LEVEL_1), + static_cast(LATENCY_LEVEL_MAX)); + } + else + { + Session.FrameSendRate = Config->FrameSendRate; + } + + Session.MaxAhead = Config->MaxAhead == -1 + ? Session.FrameSendRate * 6 + : Config->MaxAhead; + + /** + * Miscellaneous network settings. + */ + Session.MaxMaxAhead = 0; + Session.CommProtocol = 2; + Session.LatencyFudge = 0; + Session.DesiredFrameRate = 60; + TournamentGameType = static_cast(Config->Tournament); + PlanetWestwoodGameID = Config->WOLGameID; + FrameSyncSettings[GAME_IPX].Timeout = Config->ReconnectTimeout; + + /** + * For Quick Match, make sure MPDebug is off so that players can't cheat with it. + */ + if (Config->QuickMatch) + { + Session.MPlayerDebug = false; + } + + ::Init_Network(); +} + + +/** + * Reconciles loaded data with the "Players" vector. + * + * This function is for supporting loading a saved multiplayer game. + * When the game is loaded, we have to figure out which house goes with + * which entry in the Players vector. We also have to figure out if + * everyone who was originally in the game is still with us, and if not, + * turn their stuff over to the computer. + */ +bool Spawner::Reconcile_Players() +{ + int i; + bool found; + int house; + HouseClass* housep; + + /** + * If there are no players, there's nothing to do. + */ + if (Session.Players.Count() == 0) + return true; + + /** + * Make sure every name we're connected to can be found in a House. + */ + for (i = 0; i < Session.Players.Count(); i++) { + found = false; + for (house = 0; house < Session.Players.Count(); house++) { + housep = Houses[house]; + if (!housep) { + continue; + } + + if (!stricmp(Session.Players[i]->Name, housep->IniName)) { + found = true; + break; + } + } + if (!found) + return false; + } + + /** + * Loop through all Houses; if we find a human-owned house that we're + * not connected to, turn it over to the computer. + */ + for (house = 0; house < Session.Players.Count(); house++) { + housep = Houses[house]; + if (!housep) { + continue; + } + + /** + * Skip this house if it wasn't human to start with. + */ + if (!housep->IsHuman) { + continue; + } + + /** + * Try to find this name in the Players vector; if it's found, set + * its ID to this house. + */ + found = false; + for (i = 0; i < Session.Players.Count(); i++) { + if (!stricmp(Session.Players[i]->Name, housep->IniName)) { + found = true; + Session.Players[i]->Player.ID = static_cast(house); + break; + } + } + + /** + * If this name wasn't found, remove it + */ + if (!found) { + + /** + * Turn the player's house over to the computer's AI + */ + housep->IsHuman = false; + housep->IsStarted = true; + housep->IQ = Rule->MaxIQ; + + static char buffer[HOUSE_NAME_MAX + 1]; + std::snprintf(buffer, sizeof(buffer), "%s (AI)", housep->IniName); + std::strncpy(housep->IniName, buffer, sizeof(housep->IniName)); + //strcpy(housep->IniName, Fetch_String(TXT_COMPUTER)); + + Session.NumPlayers--; + } + } + + /** + * If all went well, our Session.NumPlayers value should now equal the value + * from the saved game, minus any players we removed. + */ + if (Session.NumPlayers == Session.Players.Count()) { + return true; + } + else { + return false; + } +} + + +/** + * Initializes some things for OwnerDraw UI. + * + * @author: ZivDero + */ +void Spawner::Init_UI() +{ + OwnerDraw::Init_UI_Color_Stuff_58F060(); + + if (!OwnerDraw::UIInitialized) + { + OwnerDraw::Init_Glow_Colors(); + OwnerDraw::Load_Graphics(); + OwnerDraw::UIInitialized = true; + } +} + + +/** + * Prepares the screen. + * + * @author: ZivDero + */ +void Spawner::Prepare_Screen() +{ + WWMouse->Hide_Mouse(); + + HiddenSurface->Fill(TBLACK); + GScreenClass::Blit(true, HiddenSurface); + LogicSurface = HiddenSurface; + + WWMouse->Show_Mouse(); + + Map.MouseClass::Set_Default_Mouse(MOUSE_NO_MOVE, false); + Map.MouseClass::Revert_Mouse_Shape(); + + Map.TabClass::Activate(1); + Map.SidebarClass::Flag_To_Redraw(); +} + + +/** + * Reads Houses and Sides to Rules so that we can use them to choose a loading screen. + * + * @author: ZivDero + */ +void Spawner::Read_Houses_And_Sides() +{ + Rule->Houses(*RuleINI); + Rule->Sides(*RuleINI); + + for (int i = 0; i < Houses.Count(); i++) + Houses[i]->Read_INI(*RuleINI); +} diff --git a/src/cncnet/cncnet5/cncnet5_globals.h b/src/spawner/spawner.h similarity index 60% rename from src/cncnet/cncnet5/cncnet5_globals.h rename to src/spawner/spawner.h index 7ad79c15e..016bcd099 100644 --- a/src/cncnet/cncnet5/cncnet5_globals.h +++ b/src/spawner/spawner.h @@ -4,11 +4,11 @@ * * @project Vinifera * - * @file CNCNET_GLOBALS.H + * @file SPAWNER.H * - * @author CCHyper + * @author Belonit, ZivDero * - * @brief Global values and types used for the CnCNet5 system. + * @brief Multiplayer spawner class. * * @license Vinifera is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -27,37 +27,37 @@ ******************************************************************************/ #pragma once -#include +#include "spawnerconfig.h" +#include -namespace CnCNet5 -{ - -typedef struct TunnelInfoStruct +/** + * This class contains all logic for spawning players in-game (usually via the client). + */ +class Spawner { - unsigned long ID; - unsigned long IP; - unsigned short Port; - bool PortHack; +public: + Spawner() = delete; - bool Is_Valid() const { return !(ID == -1 || IP == -1 || Port == -1); } + static bool Active; -} TunnelInfoStruct; +private: + static std::unique_ptr Config; +public: + static SpawnerConfig* Get_Config() { return Config.get(); } -/** - * Has the CnCNet5 system been activated? - */ -extern bool IsActive; + static void Init(); + static bool Start_Game(); -/** - * Is the tunnel system active (set when tunnel information has been provided)? - */ -extern bool IsTunnelActive; +private: + static bool Start_Scenario(const char* scenario_name); + static bool Load_Game(const char* file_name); -/** - * CnCNet5 UDP Tunnel info. - */ -extern TunnelInfoStruct TunnelInfo; + static void Init_Network(); + static bool Reconcile_Players(); + static void Init_UI(); + static void Prepare_Screen(); + static void Read_Houses_And_Sides(); }; diff --git a/src/spawner/spawner_hooks.cpp b/src/spawner/spawner_hooks.cpp new file mode 100644 index 000000000..136487b9d --- /dev/null +++ b/src/spawner/spawner_hooks.cpp @@ -0,0 +1,177 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file SPAWNER_HOOKS.CPP + * + * @author ZivDero + * + * @brief Contains the hooks for the multiplayer spawner class. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "spawner_hooks.h" + +#include "autosurrender_hooks.h" +#include "hooker.h" +#include "hooker_macros.h" +#include "session.h" +#include "spawner.h" +#include "house.h" +#include "housetype.h" +#include "multiscore.h" +#include "protocolzero_hooks.h" +#include "quickmatch_hooks.h" +#include "savedgamesdir_hooks.h" +#include "spectator_hooks.h" +#include "statistics_hooks.h" +#include "vinifera_globals.h" + + +/** + * A fake class for implementing new member functions which allow + * access to the "this" pointer of the intended class. + * + * @note: This must not contain a constructor or destructor. + * + * @note: All functions must not be virtual and must also be prefixed + * with "_" to prevent accidental virtualization. + */ +class SessionClassExt : public SessionClass +{ +public: + void _Read_Scenario_Descriptions(); +}; + + +/** + * Patches Read_Scenario_Descriptions to do nothing when the spawner is active. + * + * @author: ZivDero + */ +void SessionClassExt::_Read_Scenario_Descriptions() +{ + if (Spawner::Active) + return; + + SessionClass::Read_Scenario_Descriptions(); +} + + +/** + * A fake class for implementing new member functions which allow + * access to the "this" pointer of the intended class. + * + * @note: This must not contain a constructor or destructor. + * + * @note: All functions must not be virtual and must also be prefixed + * with "_" to prevent accidental virtualization. + */ +class HouseClassExt : public HouseClass +{ +public: + void _Computer_Paranoid() {} +}; + + +/** + * Patches Expert AI not the consider allies as enemies. + * + * @author: ZivDero + */ +DECLARE_PATCH(_HouseClass_Expert_AI_Check_Allies) +{ + GET_REGISTER_STATIC(HouseClass*, this_ptr, edi); + GET_REGISTER_STATIC(HouseClass*, house, esi); + + if (house != this_ptr && !house->Class->IsMultiplayPassive && !house->IsDefeated && this_ptr->Is_Ally(house)) + { + JMP(0x004C06F7); + } + + JMP(0x004C0777); +} + + +/** + * Patches the score screen to be skipped if SkipScoreScreen is set. + * + * @author: ZivDero + */ +static void MultiScore_Wrapper() +{ + if (!Spawner::Get_Config()->SkipScoreScreen) + MultiScore::Presentation(); +} + + +/** + * Main function for patching the hooks. + */ +void Spawner_Hooks() +{ + Patch_Call(0x004629D1, &Spawner::Start_Game); // Main_Game + Patch_Call(0x00462B8B, &Spawner::Start_Game); // Main_Game + + /** + * The spawner allows player to jump right into a game, so no need to + * show the startup movies. + */ + Vinifera_SkipLogoMovies = true; + Vinifera_SkipStartupMovies = true; + + Patch_Dword(0x005DB794 + 1, Spawner::Get_Config()->ConnTimeout); // Set ConnTimeout + + /** + * Skip some checks inside HouseClass::MPlayer_Defeated to make the game continue + * even if there are only allied AI players left in Skirmish. + */ + Patch_Jump(0x004BF7B6, 0x004BF7BF); + Patch_Jump(0x004BF7F0, 0x004BF7F9); + + /** + * Remove calls to SessionClass::Read_Scenario_Descriptions() when the + * spawner is active. This will speed up the initialisation and loading + * process, as PKT and MPR files are not required when using the spawner. + */ + Patch_Call(0x004E8910, &SessionClassExt::_Read_Scenario_Descriptions); // New_Main_Menu + Patch_Call(0x00564BAE, &SessionClassExt::_Read_Scenario_Descriptions); // Select_MPlayer_Game + Patch_Call(0x0057FE2A, &SessionClassExt::_Read_Scenario_Descriptions); // NewMenuClass::Process_Game_Select + Patch_Call(0x0058037C, &SessionClassExt::_Read_Scenario_Descriptions); // NewMenuClass:: + Patch_Call(0x005ED477, &SessionClassExt::_Read_Scenario_Descriptions); // SessionClass::One_Time + + Patch_Jump(0x004C06EF, &_HouseClass_Expert_AI_Check_Allies); + Patch_Jump(0x004C3630, &HouseClassExt::_Computer_Paranoid); // Disable paranoid computer behavior + + /** + * SkipScoreScreen feature. + */ + Patch_Call(0x005DC9DA, &MultiScore_Wrapper); + Patch_Call(0x005DCD98, &MultiScore_Wrapper); + + /** + * Hooks for various sub-modules. + */ + ProtocolZero_Hooks(); + Spectator_Hooks(); + QuickMatch_Hooks(); + AutoSurrender_Hooks(); + Statistics_Hooks(); + SavedGamesDir_Hooks(); +} diff --git a/src/spawner/spawner_hooks.h b/src/spawner/spawner_hooks.h new file mode 100644 index 000000000..a6db6004e --- /dev/null +++ b/src/spawner/spawner_hooks.h @@ -0,0 +1,31 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file SPAWNER_HOOKS.CPP + * + * @author ZivDero + * + * @brief Contains the hooks for the multiplayer spawner class. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ +#pragma once + + +void Spawner_Hooks(); diff --git a/src/spawner/spawnerconfig.cpp b/src/spawner/spawnerconfig.cpp new file mode 100644 index 000000000..b63f89871 --- /dev/null +++ b/src/spawner/spawnerconfig.cpp @@ -0,0 +1,231 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file SPAWNERCONFIG.CPP + * + * @author Belonit, ZivDero + * + * @brief Configuration of the multiplayer spawner. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ + +#include "spawnerconfig.h" + +#include "ccini.h" + + +/** + * Reads spawner config from the INI. + * + * @author: Belonit, ZivDero + */ +void SpawnerConfig::Read_INI(CCINIClass& spawn_ini) +{ + static char const* const SETTINGS = "Settings"; + static char const* const TUNNEL = "Tunnel"; + + /** + * Game Mode Options + */ + Bases = spawn_ini.Get_Bool(SETTINGS, "Bases", Bases); + Credits = spawn_ini.Get_Int(SETTINGS, "Credits", Credits); + BridgeDestroy = spawn_ini.Get_Bool(SETTINGS, "BridgeDestroy", BridgeDestroy); + Crates = spawn_ini.Get_Bool(SETTINGS, "Crates", Crates); + ShortGame = spawn_ini.Get_Bool(SETTINGS, "ShortGame", ShortGame); + BuildOffAlly = spawn_ini.Get_Bool(SETTINGS, "BuildOffAlly", BuildOffAlly); + GameSpeed = spawn_ini.Get_Int(SETTINGS, "GameSpeed", GameSpeed); + MultiEngineer = spawn_ini.Get_Bool(SETTINGS, "MultiEngineer", MultiEngineer); + UnitCount = spawn_ini.Get_Int(SETTINGS, "UnitCount", UnitCount); + AIPlayers = spawn_ini.Get_Int(SETTINGS, "AIPlayers", AIPlayers); + AIDifficulty = spawn_ini.Get_Int(SETTINGS, "AIDifficulty", AIDifficulty); + AlliesAllowed = spawn_ini.Get_Bool(SETTINGS, "AlliesAllowed", AlliesAllowed); + HarvesterTruce = spawn_ini.Get_Bool(SETTINGS, "HarvesterTruce", HarvesterTruce); + FogOfWar = spawn_ini.Get_Bool(SETTINGS, "FogOfWar", FogOfWar); + MCVRedeploy = spawn_ini.Get_Bool(SETTINGS, "MCVRedeploy", MCVRedeploy); + + /** + * Savegame Options + */ + LoadSaveGame = spawn_ini.Get_Bool(SETTINGS, "LoadSaveGame", LoadSaveGame); + /* SavedGamesDir */ spawn_ini.Get_String(SETTINGS, "SavedGamesDir", SavedGamesDir, SavedGamesDir, sizeof(SavedGamesDir)); + /* SaveGameName */ spawn_ini.Get_String(SETTINGS, "SaveGameName", SaveGameName, SaveGameName, sizeof(SaveGameName)); + + /** + * Scenario Options + */ + Seed = spawn_ini.Get_Int(SETTINGS, "Seed", Seed); + TechLevel = spawn_ini.Get_Int(SETTINGS, "TechLevel", TechLevel); + IsCampaign = spawn_ini.Get_Bool(SETTINGS, "IsSinglePlayer", IsCampaign); + CampaignID = spawn_ini.Get_Int(SETTINGS, "CampaignID", CampaignID); + Tournament = spawn_ini.Get_Int(SETTINGS, "Tournament", Tournament); + WOLGameID = spawn_ini.Get_Int(SETTINGS, "GameID", WOLGameID); + /* ScenarioName */ spawn_ini.Get_String(SETTINGS, "Scenario", ScenarioName, ScenarioName, sizeof(ScenarioName)); + /* MapHash */ spawn_ini.Get_String(SETTINGS, "MapHash", MapHash, MapHash, sizeof(MapHash)); + spawn_ini.Get_String(SETTINGS, "UIMapName", UIMapName, UIMapName, sizeof(UIMapName)); + + /** + * Network Options + */ + Protocol = spawn_ini.Get_Int(SETTINGS, "Protocol", Protocol); + FrameSendRate = spawn_ini.Get_Int(SETTINGS, "FrameSendRate", FrameSendRate); + ReconnectTimeout = spawn_ini.Get_Int(SETTINGS, "ReconnectTimeout", ReconnectTimeout); + ConnTimeout = spawn_ini.Get_Int(SETTINGS, "ConnTimeout", ConnTimeout); + MaxAhead = spawn_ini.Get_Int(SETTINGS, "MaxAhead", MaxAhead); + PreCalcMaxAhead = spawn_ini.Get_Int(SETTINGS, "PreCalcMaxAhead", PreCalcMaxAhead); + MaxLatencyLevel = spawn_ini.Get_Int(SETTINGS, "MaxLatencyLevel", MaxLatencyLevel); + + /** + * Tunnel Options + */ + TunnelId = spawn_ini.Get_Int(SETTINGS, "Port", TunnelId); + ListenPort = spawn_ini.Get_Int(SETTINGS, "Port", ListenPort); + /* TunnelIp */ spawn_ini.Get_String(TUNNEL, "Ip", TunnelIp, TunnelIp, sizeof(TunnelIp)); + TunnelPort = spawn_ini.Get_Int(TUNNEL, "Port", TunnelPort); + + /** + * Player and House Options + */ + for (int i = 0; i < std::size(Players); ++i) + { + Players[i].Read_INI(spawn_ini, i); + if (Players[i].IsHuman) + HumanPlayers++; + + Houses[i].Read_INI(spawn_ini, i); + } + + /** + * Extended Options + */ + Firestorm = spawn_ini.Get_Bool(SETTINGS, "Firestorm", Firestorm); + QuickMatch = spawn_ini.Get_Bool(SETTINGS, "QuickMatch", QuickMatch); + SkipScoreScreen = spawn_ini.Get_Bool(SETTINGS, "SkipScoreScreen", SkipScoreScreen); + WriteStatistics = spawn_ini.Get_Bool(SETTINGS, "WriteStatistics", WriteStatistics); + AINamesByDifficulty = spawn_ini.Get_Bool(SETTINGS, "AINamesByDifficulty", AINamesByDifficulty); + CoachMode = spawn_ini.Get_Bool(SETTINGS, "CoachMode", CoachMode); + AutoSurrender = spawn_ini.Get_Bool(SETTINGS, "AutoSurrender", AutoSurrender); +} + + +static constexpr char* PlayerSectionArray[8] = { + "Settings", + "Other1", + "Other2", + "Other3", + "Other4", + "Other5", + "Other6", + "Other7" +}; + + +static constexpr char* MultiTagArray[8] = { + "Multi1", + "Multi2", + "Multi3", + "Multi4", + "Multi5", + "Multi6", + "Multi7", + "Multi8" +}; + + +static constexpr char* AlliancesSectionArray[8] = { + "Multi1_Alliances", + "Multi2_Alliances", + "Multi3_Alliances", + "Multi4_Alliances", + "Multi5_Alliances", + "Multi6_Alliances", + "Multi7_Alliances", + "Multi8_Alliances" +}; + + +static constexpr char* AlliancesTagArray[8] = { + "HouseAllyOne", + "HouseAllyTwo", + "HouseAllyThree", + "HouseAllyFour", + "HouseAllyFive", + "HouseAllySix", + "HouseAllySeven", + "HouseAllyEight" +}; + + +/** + * Reads player's config from the INI. + * + * @author: Belonit, ZivDero + */ +void SpawnerConfig::PlayerConfig::Read_INI(CCINIClass& spawn_ini, int index) +{ + if (index >= MAX_PLAYERS) + return; + + const char* SECTION = PlayerSectionArray[index]; + const char* MULTI_TAG = MultiTagArray[index]; + + if (spawn_ini.Is_Present(SECTION)) + { + this->IsHuman = true; + this->Difficulty = -1; + + spawn_ini.Get_String(SECTION, "Name", this->Name, this->Name, sizeof(this->Name)); + + this->Color = spawn_ini.Get_Int(SECTION, "Color", this->Color); + this->House = spawn_ini.Get_Int(SECTION, "Side", this->House); + + spawn_ini.Get_String(SECTION, "Ip", this->Ip, this->Ip, sizeof(this->Ip)); + this->Port = spawn_ini.Get_Int(SECTION, "Port", this->Port); + } + else if (!IsHuman) + { + this->Color = spawn_ini.Get_Int("HouseColors", MULTI_TAG, this->Color); + this->House = spawn_ini.Get_Int("HouseCountries", MULTI_TAG, this->House); + this->Difficulty = spawn_ini.Get_Int("HouseHandicaps", MULTI_TAG, this->Difficulty); + } +} + + +/** + * Reads house's config from the INI. + * + * @author: Belonit, ZivDero + */ +void SpawnerConfig::HouseConfig::Read_INI(CCINIClass& spawn_ini, int index) +{ + if (index >= MAX_PLAYERS) + return; + + const char* ALLIANCES = AlliancesSectionArray[index]; + const char* MULTI_TAG = MultiTagArray[index]; + + this->IsSpectator = spawn_ini.Get_Bool("IsSpectator", MULTI_TAG, this->IsSpectator); + this->SpawnLocation = spawn_ini.Get_Int("SpawnLocations", MULTI_TAG, SpawnLocation); + + if (spawn_ini.Is_Present(ALLIANCES)) + { + for(int i = 0; i < 8; i++) + this->Alliances[i] = spawn_ini.Get_Int(ALLIANCES, AlliancesTagArray[i], this->Alliances[i]); + } +} diff --git a/src/spawner/spawnerconfig.h b/src/spawner/spawnerconfig.h new file mode 100644 index 000000000..73cf3dc79 --- /dev/null +++ b/src/spawner/spawnerconfig.h @@ -0,0 +1,246 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file SPAWNERCONFIG.H + * + * @author Belonit, ZivDero + * + * @brief Configuration of the multiplayer spawner. + * + * @license Vinifera 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 + * 3 of the License, or (at your option) any later version. + * + * Vinifera 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 this program. + * If not, see . + * + ******************************************************************************/ +#pragma once + +#include "abstractext.h" + +class CCINIClass; + + +/** + * This class contains all the configuration for the spawner, usually read from SPAWN.INI. + */ +class SpawnerConfig +{ + /** + * Used to create NodeNameType + * The order of entries may differ from HouseConfig + */ + struct PlayerConfig + { + bool IsHuman; + char Name[20]; + int Color; + int House; + int Difficulty; + char Ip[0x20]; + int Port; + + PlayerConfig() + : IsHuman { false } + , Name { "" } + , Color { -1 } + , House { -1 } + , Difficulty { -1 } + , Ip { "0.0.0.0" } + , Port { -1 } + { } + + void Read_INI(CCINIClass& spawn_ini, int index); + }; + + /** + * Used to configure the generated HouseClass + * Must be sorted by respective player color + */ + struct HouseConfig + { + bool IsSpectator; + int SpawnLocation; + int Alliances[8]; + + HouseConfig() + : IsSpectator { false } + , SpawnLocation { -2 } + , Alliances { -1, -1, -1, -1, -1, -1, -1, -1 } + { } + + void Read_INI(CCINIClass& spawn_ini, int index); + }; + +public: + /** + * Game Mode Options + */ + bool Bases; + int Credits; + bool BridgeDestroy; + bool Crates; + bool ShortGame; + bool BuildOffAlly; + int GameSpeed; + bool MultiEngineer; + int UnitCount; + int AIPlayers; + int AIDifficulty; + bool AlliesAllowed; + bool HarvesterTruce; + bool FogOfWar; + bool MCVRedeploy; + + /** + * Savegame Options + */ + bool LoadSaveGame; + char SavedGamesDir[MAX_PATH]; // Nested paths are also supported, e.g. "Saved Games\\Tiberian Sun" + char SaveGameName[60]; + + /** + * Scenario Options + */ + int Seed; + int TechLevel; + bool IsCampaign; + int CampaignID; + int Tournament; + unsigned int WOLGameID; + char ScenarioName[260]; + char MapHash[0xff]; + char UIMapName[44]; + + /** + * Network Options + */ + int Protocol; + int FrameSendRate; + int ReconnectTimeout; + int ConnTimeout; + int MaxAhead; + int PreCalcMaxAhead; + unsigned char MaxLatencyLevel; + + /** + * Tunnel Options + */ + int TunnelId; + char TunnelIp[0x20]; + int TunnelPort; + int ListenPort; + + /** + * Player Options + */ + PlayerConfig Players[8]; + int HumanPlayers; + + /** + * House Options + */ + HouseConfig Houses[8]; + + /** + * Extended Options + */ + bool Firestorm; + bool QuickMatch; + bool SkipScoreScreen; + bool WriteStatistics; + bool AINamesByDifficulty; + bool CoachMode; + bool AutoSurrender; + + SpawnerConfig() + : Bases { true } + , Credits { 10000 } + , BridgeDestroy { true } + , Crates { false } + , ShortGame { false } + , BuildOffAlly { false } + , GameSpeed { 0 } + , MultiEngineer { false } + , UnitCount { 0 } + , AIPlayers { 0 } + , AIDifficulty { 1 } + , AlliesAllowed { false } + , HarvesterTruce { false } + , FogOfWar { false } + , MCVRedeploy { true } + + , LoadSaveGame { false } + , SavedGamesDir { "Saved Games" } + , SaveGameName { "" } + + , Seed { 0 } + , TechLevel { 10 } + , IsCampaign { false } + , CampaignID { -1 } + , Tournament { 0 } + , WOLGameID { 0xDEADBEEF } + , ScenarioName { "spawnmap.ini" } + , MapHash { "" } + , UIMapName { "" } + + , Protocol { 2 } + , FrameSendRate { 4 } + , ReconnectTimeout { 2400 } + , ConnTimeout { 3600 } + , MaxAhead { -1 } + , PreCalcMaxAhead { 0 } + , MaxLatencyLevel { 0xFF } + + , TunnelId { 0 } + , TunnelIp { "0.0.0.0" } + , TunnelPort { 0 } + , ListenPort { 1234 } + + , Players { + PlayerConfig(), + PlayerConfig(), + PlayerConfig(), + PlayerConfig(), + + PlayerConfig(), + PlayerConfig(), + PlayerConfig(), + PlayerConfig() + } + , HumanPlayers(0) + + , Houses { + HouseConfig(), + HouseConfig(), + HouseConfig(), + HouseConfig(), + + HouseConfig(), + HouseConfig(), + HouseConfig(), + HouseConfig() + } + + , Firestorm { true } + , QuickMatch { false } + , SkipScoreScreen { false } + , WriteStatistics { false } + , AINamesByDifficulty { false } + , CoachMode { false } + , AutoSurrender { true } + { } + + void Read_INI(CCINIClass& spawn_ini); +}; diff --git a/src/vinifera/vinifera_functions.cpp b/src/vinifera/vinifera_functions.cpp index eeef2a41a..956ff02eb 100644 --- a/src/vinifera/vinifera_functions.cpp +++ b/src/vinifera/vinifera_functions.cpp @@ -29,9 +29,6 @@ #include "vinifera_globals.h" #include "vinifera_newdel.h" #include "tibsun_globals.h" -#include "cncnet4.h" -#include "cncnet4_globals.h" -#include "cncnet5_globals.h" #include "rulesext.h" #include "ccfile.h" #include "ccini.h" @@ -60,6 +57,8 @@ #include "rocketlocomotion.h" #include "setup_hooks.h" +#include "spawner.h" +#include "spawner_hooks.h" static DynamicVectorClass ViniferaSearchPaths; @@ -297,6 +296,16 @@ bool Vinifera_Parse_Command_Line(int argc, char *argv[]) continue; } + /** + * Start in spawner mode. + */ + if (stricmp(string, "-SPAWN") == 0) { + DEBUG_INFO(" - Spawner enabled.\n"); + Spawner::Init(); + Spawner_Hooks(); + continue; + } + /** * Skip the startup videos. */ @@ -501,39 +510,11 @@ bool Vinifera_Startup() ViniferaSearchPaths.Add("TS3"); #endif - /** - * #issue-514: - * - * Adds various search paths for loading files locally for the TS-Client builds only. - * - * #NOTE: REMOVED: Additional paths must now be set via SearchPaths in VINIFERA.INI! - * - * @author: CCHyper - */ -#if 0 // #if defined(TS_CLIENT) - - // Only required for the TS Client builds as most projects will - // put VINIFERA.INI in this directory. - ViniferaSearchPaths.Add("INI"); - - // Required for startup mix files to be found. - ViniferaSearchPaths.Add("MIX"); -#endif - #if !defined(TS_CLIENT) // Required for startup movies to be found. ViniferaSearchPaths.Add("MOVIES"); #endif - // REMOVED: Paths are now set via SearchPaths in VINIFERA.INI -//#if defined(TS_CLIENT) -// ViniferaSearchPaths.Add("MUSIC"); -// ViniferaSearchPaths.Add("SOUNDS"); -// ViniferaSearchPaths.Add("MAPS"); -// ViniferaSearchPaths.Add("MAPS\\MULTIPLAYER"); -// ViniferaSearchPaths.Add("MAPS\\MISSION"); -//#endif - /** * Load Vinifera settings and overrides. */ @@ -614,32 +595,8 @@ bool Vinifera_Startup() return false; } -#if !defined(TS_CLIENT) - /** - * Initialise the CnCNet4 system. - */ - if (!CnCNet4::Init()) { - CnCNet4::IsEnabled = false; - DEBUG_WARNING("Failed to initialise CnCNet4, continuing without CnCNet4 support!\n"); - } - - /** - * Disable CnCNet4 if CnCNet5 is active, they can not co-exist. - */ - if (CnCNet4::IsEnabled && CnCNet5::IsActive) { - CnCNet4::Shutdown(); - CnCNet4::IsEnabled = false; - } -#else - /** - * Client builds can only use CnCNet5. - */ - CnCNet4::IsEnabled = false; - //CnCNet5::IsActive = true; // Enable when new Client system is implemented. -#endif - KamikazeTracker = new KamikazeTrackerClass; - + return true; } @@ -721,13 +678,6 @@ int Vinifera_Pre_Init_Game(int argc, char *argv[]) DEV_DEBUG_WARNING("UI.INI not found!\n"); } -#if defined(TS_CLIENT) - /** - * The TS Client allows player to jump right into a game, so no need to - * show the startup movies for these builds. - */ - Vinifera_SkipStartupMovies = true; -#endif /** * Read the mouse controls and overrides. diff --git a/src/vinifera/vinifera_hooks.cpp b/src/vinifera/vinifera_hooks.cpp index 2d5360a95..1e4b38380 100644 --- a/src/vinifera/vinifera_hooks.cpp +++ b/src/vinifera/vinifera_hooks.cpp @@ -53,6 +53,7 @@ #include "spawnmanager.h" #include "armortype.h" #include "rockettype.h" +#include "spawner.h" /** @@ -708,20 +709,6 @@ void Vinifera_Hooks() Patch_Byte(0x0070EEAB, num); Patch_Byte(0x0070EF0F, num); -#if defined(TS_CLIENT) - /** - * Remove calls to SessionClass::Read_Scenario_Descriptions() in TS Client - * compatable builds. This will speed up the initialisation and loading - * process, as the reason of PKT and MPR files are not required when using - * the Client. - */ - Patch_Byte_Range(0x004E8901, 0x90, 5); // NewMenu::Process - Patch_Byte_Range(0x004E8910, 0x90, 5); // ^ - Patch_Byte_Range(0x00564BA9, 0x90, 10); // Select_MPlayer_Game - Patch_Byte_Range(0x0057FE2A, 0x90, 10); // NewMenuClass::Process_Game_Select - Patch_Byte_Range(0x00580377, 0x90, 10); // NewMenuClass::Process_Game_Select -#endif - /** * Various patches to intercept the games object tracking and heap processing. */ diff --git a/src/vinifera/vinifera_saveload.cpp b/src/vinifera/vinifera_saveload.cpp index 942ba563f..37bdff548 100644 --- a/src/vinifera/vinifera_saveload.cpp +++ b/src/vinifera/vinifera_saveload.cpp @@ -134,6 +134,10 @@ #include "savever.h" #include "vinifera_savever.h" #include "windialog.h" +#include "fetchres.h" +#include "spawner.h" +#include "technoext.h" +#include "verses.h" /** diff --git a/src/vinifera/vinifera_util.cpp b/src/vinifera/vinifera_util.cpp index b9ee29085..1b8f379ff 100644 --- a/src/vinifera/vinifera_util.cpp +++ b/src/vinifera/vinifera_util.cpp @@ -38,7 +38,6 @@ #include "spritecollection.h" #include "filepng.h" #include "filepcx.h" -#include "cncnet4_globals.h" #include "wwfont.h" #include "msgbox.h" #include "minidump.h" @@ -61,26 +60,16 @@ const char *Vinifera_Name_String() if (_buffer[0] == '\0') { - /** - * Append the CnCNet version if enabled. - */ - char *cncnet_mode = nullptr; - if (CnCNet4::IsEnabled) { - cncnet_mode = " (CnCNet4)"; - } - char *dev_mode = nullptr; if (Vinifera_DeveloperMode) { dev_mode = " (Dev)"; } - if (!dev_mode && !cncnet_mode) { + if (!dev_mode) { std::snprintf(_buffer, sizeof(_buffer), "Vinifera"); } else { - std::snprintf(_buffer, sizeof(_buffer), "Vinifera:%s%s", - cncnet_mode != nullptr ? cncnet_mode : "", - dev_mode != nullptr ? dev_mode : ""); + std::snprintf(_buffer, sizeof(_buffer), "Vinifera:%s", dev_mode != nullptr ? dev_mode : ""); } #if defined(TS_CLIENT) @@ -164,24 +153,14 @@ const char *Vinifera_Version_String() static char _buffer[512] { '\0' }; if (_buffer[0] == '\0') { - - /** - * Append the CnCNet version if enabled. - */ - char *cncnet_mode = nullptr; - if (CnCNet4::IsEnabled) { - cncnet_mode = " (CnCNet4)"; - } #ifndef RELEASE - std::snprintf(_buffer, sizeof(_buffer), "Vinifera:%s%s - %s %s %s%s %s", - cncnet_mode != nullptr ? cncnet_mode : "", + std::snprintf(_buffer, sizeof(_buffer), "Vinifera:%s - %s %s %s%s %s", Vinifera_DeveloperMode ? " (Dev)" : "", Vinifera_Git_Branch(), Vinifera_Git_Author(), Vinifera_Git_Uncommitted_Changes() ? "~" : "", Vinifera_Git_Hash_Short(), Vinifera_Git_DateTime()); #else - std::snprintf(_buffer, sizeof(_buffer), "Vinifera:%s%s - %s%s %s", - cncnet_mode != nullptr ? cncnet_mode : "", + std::snprintf(_buffer, sizeof(_buffer), "Vinifera:%s - %s%s %s", Vinifera_DeveloperMode ? " (Dev)" : "", Vinifera_Git_Uncommitted_Changes() ? "~" : "", Vinifera_Git_Hash_Short(), Vinifera_Git_DateTime()); #endif