OnlineCTR v1020 (#194)
Some checks failed
Build main / check (push) Has been cancelled

* onlinectr v1020 initial commit. some singleplayer testing seems to work, need to integrate with launcher, more multiplayer testing, update hosted assets, notify server hosts, write discord announcement.

* add back gpu defer.

* fix "load" screen crash when pressing select.

* Enable items on page 2.

* retrofueled working

* collisions no longer happen in itemless rooms.

* remove gpu defer

* disable online beta mode, fix launcher not applying settings.ini correctly.

* fix domain target

* bump launcher version

* some more testing, remove dead code, spectator mode now has player name at bottom of screen instead of jittery names3d

* special case to allow v1020 clients to connect to v1019 servers (they're compatible), change filehost domain, reduce client.exe startup delay by 2s

* uncomment USE_ONLINE from the merge.

* update filehost back to www.online-ctr.com
This commit is contained in:
TheUbMunster 2024-11-02 08:08:06 -06:00 committed by GitHub
parent efe796e0a2
commit 9601b2bff8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 730 additions and 1545 deletions

View File

@ -3,6 +3,7 @@
#include <common.h>
#ifdef USE_ONLINE
#include "../AltMods/OnlineCTR/global.h"
Color HsvToRgb(int h, int s, int v);
void FixReservesIncrement(struct Driver * driver, int reserves)
@ -10,6 +11,19 @@ void FixReservesIncrement(struct Driver * driver, int reserves)
if (driver->reserves > 30000) { driver->uncappedReserves += reserves; }
else { driver->reserves += reserves; }
}
void Online_CollidePointWithBucket(struct Thread* th, short* vec3_pos)
{
int rn = octr->serverRoom;
if (!ROOM_IS_ITEMS(rn)) //itemless rooms have no collision.
return;
while (th != 0)
{
DECOMP_PROC_CollidePointWithSelf(th, vec3_pos);
// next
th = th->siblingThread;
}
}
#endif
#ifdef USE_BOOSTBAR

View File

@ -24,7 +24,8 @@ void EndOfRace_Camera()
const char s_switchCam[] = "Press R1 or L1 to change the camera";
DECOMP_DecalFont_DrawLine(s_switchCam, 0x100, 5, FONT_SMALL, JUSTIFY_CENTER | ORANGE);
struct GamepadBuffer * pad = &sdata->gGamepads->gamepad[0];
DECOMP_DecalFont_DrawLine(octr->nameBuffer[currCam], 252, 195, FONT_BIG, JUSTIFY_CENTER | PAPU_YELLOW);
struct GamepadBuffer* pad = &sdata->gGamepads->gamepad[0];
if (pad->buttonsTapped & BTN_R1) { SetNextCamera(true); }
if (pad->buttonsTapped & BTN_L1) { SetNextCamera(false); }
}

View File

@ -1,7 +1,7 @@
#ifndef ONLINE_GLOBAL_H
#define ONLINE_GLOBAL_H
#define VERSION 1019
#define VERSION 1020
//#define ONLINE_BETA_MODE
#ifndef WINDOWS_INCLUDE
@ -32,6 +32,35 @@
#define IP_ADDRESS_SIZE 16 // assuming IPv4 (which is "xxx.xxx.xxx.xxx" + '\0')
#define PORT_SIZE 6 // the port number as a string (0-65535 + '\0')
//these let you adjust the number of rooms allocated to game types
//"START" is the starting index [0-15]
//"LENGTH" is the length of that game type.
//itemless rooms are 0 1 2 3 4
#define ROOM_ITEMLESSSTART 0
#define ROOM_ITEMLESSLENGTH 5
//item rooms are 5 6 7 8 9
#define ROOM_ITEMSTART 5
#define ROOM_ITEMLENGTH 5
//retro rooms are 10 11 12
#define ROOM_RETROSTART 10
#define ROOM_RETROLENGTH 3
//item + retro rooms are 13 14 15
#define ROOM_ITEMRETROSTART 13
#define ROOM_ITEMRETROLENGTH 3
#define ROOM_IS_ITEMS(rn) ((rn >= ROOM_ITEMSTART && rn < (ROOM_ITEMSTART + ROOM_ITEMLENGTH)) || \
(rn >= ROOM_ITEMRETROSTART && rn < (ROOM_ITEMRETROSTART + ROOM_ITEMRETROLENGTH)))
#define ROOM_IS_RETRO(rn) ((rn >= ROOM_RETROSTART && rn < (ROOM_RETROSTART + ROOM_RETROLENGTH)) || \
(rn >= ROOM_ITEMRETROSTART && rn < (ROOM_ITEMRETROSTART + ROOM_ITEMRETROLENGTH)))
// 2 seconds to be very tolerant on client
#ifdef USE_60FPS
#define DISCONNECT_AT_UNSYNCED_FRAMES 120
#else
#define DISCONNECT_AT_UNSYNCED_FRAMES 60
#endif
enum ClientState
{
LAUNCH_ENTER_PID,
@ -61,7 +90,7 @@ typedef struct raceStats
} raceStats;
// This can be 0x400 (1024) bytes max:
// 0x8000C000 at 0x8000C400
// 0x8000C000 to 0x8000C400
struct OnlineCTR
{
// 0x0
@ -135,9 +164,8 @@ struct OnlineCTR
char desiredFPS;
#ifdef PINE_DEBUG
int stateChangeCounter;
#endif
// when to start the client.exe loop
int readyToSend;
};
STATIC_ASSERT2(sizeof(struct OnlineCTR) <= 0x400, "Size of OnlineCTR must be lte 1kb");

View File

@ -167,12 +167,9 @@ void OnlineInit_Drivers(struct GameTracker* gGT)
#endif
}
if (gGT->levelID != 0x26)
if (gGT->levelID != 0x26/*INTRO_RACE_TODAY*/) //was 0x26 (globe level)
{
octr->CurrState = GAME_WAIT_FOR_RACE;
#ifdef PINE_DEBUG
printf("statechange %d GAME_WAIT_FOR_RACE 1: \n", octr->stateChangeCounter++);
#endif
}
}
@ -188,16 +185,25 @@ bool HasRaceEnded()
RECT windowText = {0x118, 0x40, 0xD8, 0};
//extern int currCam; //from endOfRaceUI.c
void OnlineEndOfRace()
{
struct Driver * driver = sdata->gGT->drivers[0];
if (((driver->actionsFlagSet & 0x2000000) == 0) ||
(octr->CurrState < GAME_START_RACE)) { return; }
//this is a potential untested fix for the "spectator name bug"
//(i.e.), when first beginning to spectate, the name of the person you're
//spectating at the bottom of the screen can sometimes be incorrect until you
//change the camera for the first time.
//if (octr->CurrState != GAME_END_RACE) //this must be out first frame here.
//{
// currCam = 0;
// sdata->gGT->cameraDC[0].driverToFollow = sdata->gGT->drivers[currCam];
//}
octr->CurrState = GAME_END_RACE;
#ifdef PINE_DEBUG
printf("statechange %d GAME_END_RACE 2: \n", octr->stateChangeCounter++);
#endif
static unsigned frameCounter = 0;
EndOfRace_Camera();

View File

@ -82,7 +82,7 @@ void NewPage_ServerCountry()
{
int i;
menu.posX_curr = 0x198; // X position
menu.posX_curr = 0x188; // X position
menu.posY_curr = 0x84; // Y position
// override "LAPS" "3/5/7",
@ -141,27 +141,48 @@ void NewPage_ServerRoom()
{
int i;
// override "LAPS" "3/5/7"
sdata->lngStrings[0x9a] = "ROOM 1 - x/8";
sdata->lngStrings[0x9b] = "ROOM 2 - x/8";
sdata->lngStrings[0x9c] = "ROOM 3 - x/8";
sdata->lngStrings[0x9d] = "ROOM 4 - x/8";
sdata->lngStrings[0x9e] = "ROOM 5 - x/8";
sdata->lngStrings[0x9f] = "ROOM 6 - x/8";
sdata->lngStrings[0xa0] = "ROOM 7 - x/8";
sdata->lngStrings[0xa1] = "ROOM 8 - x/8";
int pn = octr->PageNumber;
const char* itemless = "ITEMLESS";
const char* items = "ITEMS ";
const char* retro = "RETRO ";
const char* itmretro = "ITEM+RET";
sdata->lngStrings[0x9a] = "ROOM 1 - x/8";
sdata->lngStrings[0x9b] = "ROOM 2 - x/8";
sdata->lngStrings[0x9c] = "ROOM 3 - x/8";
sdata->lngStrings[0x9d] = "ROOM 4 - x/8";
sdata->lngStrings[0x9e] = "ROOM 5 - x/8";
sdata->lngStrings[0x9f] = "ROOM 6 - x/8";
sdata->lngStrings[0xa0] = "ROOM 7 - x/8";
sdata->lngStrings[0xa1] = "ROOM 8 - x/8";
for (i = 0; i < 8; i++)
{
int rn = i + (pn * 8);
const char* type;
if (rn >= ROOM_ITEMLESSSTART && rn < (ROOM_ITEMLESSSTART + ROOM_ITEMLESSLENGTH))
type = itemless;
if (rn >= ROOM_ITEMSTART && rn < (ROOM_ITEMSTART + ROOM_ITEMLENGTH))
type = items;
if (rn >= ROOM_RETROSTART && rn < (ROOM_RETROSTART + ROOM_RETROLENGTH))
type = retro;
if (rn >= ROOM_ITEMRETROSTART && rn < (ROOM_ITEMRETROSTART + ROOM_ITEMRETROLENGTH))
type = itmretro;
for (int name = 0; name < 8; name++)
{
sdata->lngStrings[0x9a+i][name] = type[name]; //replace "ROOM" with the roomtype
}
}
for(i = 0; i < 8; i++)
{
menuRows[i].stringIndex = 0x809a+i;
sdata->lngStrings[0x9a+i][5] = GetRoomChar(8*pn + i+1);
sdata->lngStrings[0x9a+i][9] = '0' + (octr->clientCount[8*pn+i]);
sdata->lngStrings[0x9a+i][9] = GetRoomChar(8*pn + i+1);
sdata->lngStrings[0x9a+i][13] = '0' + (octr->clientCount[8*pn+i]);
// handle locked rows
if(octr->clientCount[8*pn+i] > 8)
sdata->lngStrings[0x9a+i][9] = '0' + (octr->clientCount[8*pn+i]) - 8;
sdata->lngStrings[0x9a+i][13] = '0' + (octr->clientCount[8*pn+i]) - 8;
}
int numRooms = GetNumRoom();

View File

@ -64,8 +64,8 @@ void ResetPsxGlobals()
void StatePS1_Launch_PickRoom()
{
#if 0
DecalFont_DrawLine("Special Events in odd rooms: 1,3,5...",0x100,0x14,FONT_SMALL,JUSTIFY_CENTER|PAPU_YELLOW);
DecalFont_DrawLine("Classic Games in even rooms: 2,4,6...",0x100,0x1c,FONT_SMALL,JUSTIFY_CENTER|PAPU_YELLOW);
DecalFont_DrawLine("Itemless games on first page",0x100,0x14,FONT_SMALL,JUSTIFY_CENTER|PAPU_YELLOW);
DecalFont_DrawLine("Items games on second page",0x100,0x1c,FONT_SMALL,JUSTIFY_CENTER|PAPU_YELLOW);
#endif
MenuWrites_ServerRoom();
@ -126,17 +126,11 @@ void StatePS1_Lobby_AssignRole()
if(octr->DriverID == 0)
{
octr->CurrState = LOBBY_HOST_TRACK_PICK;
#ifdef PINE_DEBUG
printf("statechange %d LOBBY_HOST_TRACK_PICK 3: \n", octr->stateChangeCounter++);
#endif
}
else if (octr->DriverID > 0)
{
octr->CurrState = LOBBY_GUEST_TRACK_WAIT;
#ifdef PINE_DEBUG
printf("statechange %d LOBBY_GUEST_TRACK_WAIT 4: \n", octr->stateChangeCounter++);
#endif
}
}
@ -349,7 +343,9 @@ void StatePS1_Game_WaitForRace()
}
gGT->trafficLightsTimer = 0xf40;
Ghostify();
int rn = octr->serverRoom;
if (!ROOM_IS_ITEMS(rn)) //itemless only
Ghostify();
if((gGT->gameMode1 & START_OF_RACE) != 0)
return;
@ -386,7 +382,10 @@ void StatePS1_Game_WaitForRace()
void StatePS1_Game_StartRace()
{
int i;
Ghostify();
int rn = octr->serverRoom;
if (!ROOM_IS_ITEMS(rn)) //itemless only
Ghostify();
for(i = 1; i < 8; i++)
{
@ -426,7 +425,8 @@ void StatePS1_Game_StartRace()
static void OnRaceEnd()
{
struct Driver ** drivers = sdata->gGT->drivers;
struct Driver* before = sdata->gGT->cameraDC[0].driverToFollow;
struct Driver** drivers = sdata->gGT->drivers;
bool foundRacer = false;
for (int driverID = 1; driverID < MAX_NUM_PLAYERS; driverID++)
{
@ -439,6 +439,8 @@ static void OnRaceEnd()
foundRacer = true;
}
}
//tbh I don't think this fixes the spectator name bug.
sdata->gGT->cameraDC[0].driverToFollow = before;
}
void StatePS1_Game_EndRace()

View File

@ -32,28 +32,30 @@ void ThreadFunc(struct Thread* t)
int isIdle = 0;
struct GameTracker* gGT = sdata->gGT;
octr->boolPlanetLEV = gGT->levelID == 0x26;
octr->boolPlanetLEV = gGT->levelID == 0x26; //was 0x26 (globe level)
if(octr->boolPlanetLEV)
{
*(int*)0x800ae54c = 0x3e00008;
*(int*)0x800ae550 = 0;
//this code seems to be causing the LOAD screen crash.
//*(int*)0x800ae54c = 0x3e00008;
//*(int*)0x800ae550 = 0;
// freecam mode
gGT->cameraDC[0].cameraMode = 3;
//// freecam mode
//gGT->cameraDC[0].cameraMode = 3;
// disable all HUD flags
gGT->hudFlags = 0;
//// disable all HUD flags
//gGT->hudFlags = 0;
struct PushBuffer* pb = &gGT->pushBuffer[0];
//struct PushBuffer* pb = &gGT->pushBuffer[0];
pb->pos[0] = 0x3D;
pb->pos[1] = 0xF8;
pb->pos[2] = 0xF879;
//pb->pos[0] = 0x3D;
//pb->pos[1] = 0xF8;
//pb->pos[2] = 0xF879;
pb->rot[0] = 0x841;
pb->rot[1] = 0x77c;
pb->rot[2] = 0xff5;
//pb->rot[0] = 0x841;
//pb->rot[1] = 0x77c;
//pb->rot[2] = 0xff5;
}
// only disable for no$psx testing,
@ -84,34 +86,8 @@ void ThreadFunc(struct Thread* t)
octr->frames_unsynced = 0;
}
//this debug info courtesy of ClaudioBo
#ifdef PINE_DEBUG
static unsigned frameCounter = 0;
char frames_unsynced_str[12];
sprintf(frames_unsynced_str, "%d", octr->frames_unsynced);
if (octr->frames_unsynced >= 80) {
int color = frameCounter++ & FPS_DOUBLE(1) ? RED : WHITE;
char final_str[20];
sprintf(final_str, "WTF!!: %s/120", frames_unsynced_str);
DECOMP_DecalFont_DrawLine(final_str, 0x100, 200, FONT_BIG, JUSTIFY_CENTER | color);
}
else if (octr->frames_unsynced >= 60) {
DECOMP_DecalFont_DrawLine(frames_unsynced_str, 0x100, 200, FONT_BIG, JUSTIFY_CENTER | CORTEX_RED);
}
else if (octr->frames_unsynced >= 40) {
DECOMP_DecalFont_DrawLine(frames_unsynced_str, 0x100, 200, FONT_BIG, JUSTIFY_CENTER | ROO_ORANGE);
}
else if (octr->frames_unsynced >= 20) {
DECOMP_DecalFont_DrawLine(frames_unsynced_str, 0x100, 200, FONT_BIG, JUSTIFY_CENTER | PAPU_YELLOW);
}
else {
DECOMP_DecalFont_DrawLine(frames_unsynced_str, 0x100, 200, FONT_BIG, JUSTIFY_CENTER | WHITE);
}
#endif
// close if client didn't update the game in 2 seconds
int boolCloseClient = (octr->frames_unsynced > FPS_DOUBLE(60));
// close if client didn't update the game in DISCONNECT_AT_UNSYNCED_FRAMES
int boolCloseClient = (octr->frames_unsynced > DISCONNECT_AT_UNSYNCED_FRAMES);
// if client closed, or server disconnected
if(boolCloseClient || (octr->CurrState < 0))
@ -123,9 +99,6 @@ void ThreadFunc(struct Thread* t)
// if closed==1, go to 0 ("please open client")
// if closed==0, go to 1 (server select)
octr->CurrState = !boolCloseClient;
#ifdef PINE_DEBUG
printf("statechange %d yesno open client/server select 5: \n", octr->stateChangeCounter++);
#endif
octr->serverLockIn1 = 0;
octr->serverLockIn2 = 0;
@ -138,9 +111,6 @@ void ThreadFunc(struct Thread* t)
// if closed==1, go to 0 ("please open client")
// if closed==0, go to 1 (server select)
octr->CurrState = !boolCloseClient;
#ifdef PINE_DEBUG
printf("statechange %d yesno open client/server select 6: \n", octr->stateChangeCounter++);
#endif
// stop music,
// stop "most FX", let menu FX ring
@ -163,7 +133,7 @@ void ThreadFunc(struct Thread* t)
#endif
// gameplay
if (octr->CurrState >= GAME_WAIT_FOR_RACE)
if (octr->CurrState >= GAME_WAIT_FOR_RACE && octr->CurrState < GAME_END_RACE)
{
void DrawOverheadNames();
DrawOverheadNames();

View File

@ -15,7 +15,7 @@ void DECOMP_DecalFont_DrawLineStrlen(u_char* str, short len, int posX, short pos
}
// bug fix exclusive to versions after USA Retail
#if BUILD >= JpnTrial
#if BUILD >= JpnTrial || defined(USE_ONLINE) //use this fix in online, even though not Jpn.
flags &= 0x7ff;

View File

@ -204,10 +204,7 @@ LAB_80035098:
(gGT->threadBuckets[iVar4].thread != 0)
)
{
// online multiplayer
#ifdef USE_ONLINE
#if defined(USE_ONLINE)
// synchronize track hazards
if(
(iVar4 == STATIC) ||
@ -217,33 +214,7 @@ LAB_80035098:
if(gGT->trafficLightsTimer > 3600)
continue;
}
if (iVar4 == 0)
{
struct Driver* dOnline = gGT->drivers[0];
if(dOnline != 0)
{
struct Thread* dThread = dOnline->instSelf->thread;
DECOMP_VehPickupItem_ShootOnCirclePress(dOnline);
RunVehicleSet13(dThread, dOnline);
octr->desiredFPS = FPS_DOUBLE(30);
}
for(int other = 1; other < 8; other++)
{
dOnline = gGT->drivers[other];
if(dOnline == 0) continue;
struct Thread* dThread = dOnline->instSelf->thread;
RunVehicleSet13(dThread, dOnline);
}
}
// offline
#else
#endif
if (iVar4 == 0)
{
@ -312,8 +283,11 @@ LAB_80035098:
#ifdef USE_HIGHMP
gGT->numPlyrCurrGame = backupPlyrCount;
#endif
#ifdef USE_ONLINE
octr->readyToSend = 1;
#endif
}
#endif
#ifndef REBUILD_PS1

View File

@ -1,15 +1,17 @@
#include <common.h>
#ifdef USE_ONLINE
#include "../AltMods/OnlineCTR/global.h"
void Online_CollidePointWithBucket(struct Thread* th, short* vec3_pos);
#endif
void DECOMP_PROC_CollidePointWithBucket(struct Thread* th, short* vec3_pos)
{
struct Thread* other;
// only used with drivers colliding
// with other drivers, disabled online
#ifdef USE_ONLINE
return;
#endif
// only used with drivers colliding
// with other drivers, disabled online
#if defined(USE_ONLINE)
Online_CollidePointWithBucket(th, vec3_pos);
#else
while(th != 0)
{
DECOMP_PROC_CollidePointWithSelf(th, vec3_pos);
@ -17,4 +19,5 @@ void DECOMP_PROC_CollidePointWithBucket(struct Thread* th, short* vec3_pos)
// next
th = th->siblingThread;
}
#endif
}

View File

@ -1,6 +1,7 @@
#include <common.h>
#ifdef USE_ONLINE
#include "../AltMods/OnlineCTR/global.h"
void FixReservesIncrement(struct Driver * driver, int reserves);
#endif
@ -262,6 +263,10 @@ void DECOMP_VehFire_Increment(struct Driver* driver, int reserves, u_int type, i
#endif
);
#if defined(USE_RETROFUELED) && defined(USE_ONLINE)
int rn = octr->serverRoom;
int doRetroFueled = ROOM_IS_RETRO(rn);
#endif
if
(
// any gain in boost,
@ -284,6 +289,9 @@ void DECOMP_VehFire_Increment(struct Driver* driver, int reserves, u_int type, i
// You are not on a super turbo pad
(int)driver->const_SacredFireSpeed < (int)driver->fireSpeedCap &&
((driver->stepFlagSet & 2) == 0)
#if defined(USE_RETROFUELED) && defined(USE_ONLINE)
&& !doRetroFueled //is not retrofueled mode
#endif
)
)
@ -347,11 +355,12 @@ void DECOMP_VehFire_Increment(struct Driver* driver, int reserves, u_int type, i
}
}
#ifdef USE_ONLINE
if(driver->driverID != 0)
#if defined(USE_ONLINE)
if(driver->driverID != 0) //if not ourself
return;
#endif
//#if !defined (USE_ONLINE) //uncomment this if you need bytebudget.
// if modelIndex == "player" of any kind
if (driver->instSelf->thread->modelIndex == 0x18)
{
@ -361,4 +370,5 @@ void DECOMP_VehFire_Increment(struct Driver* driver, int reserves, u_int type, i
// gamepad vibration
DECOMP_GAMEPAD_ShockForce1(driver, 8, 0x7f);
}
//#endif
}

View File

@ -53,10 +53,14 @@ void DECOMP_VehPhysGeneral_SetHeldItem(struct Driver* driver) {
// Choose Itemset based on number of Drivers
int mode = gGT->numPlyrCurrGame + gGT->numBotsNextGame;
#if 0 && defined(USE_ONLINE)
mode = octr->NumDrivers;
if(octr->NumDrivers == 1) mode = 2;
if(octr->NumDrivers == 7) mode = 8;
#if /*0 &&*/ defined(USE_ONLINE)
int rn = octr->serverRoom;
if (ROOM_IS_ITEMS(rn)) //if in item lobby.
{
mode = octr->NumDrivers;
if (octr->NumDrivers == 1) mode = 2; //why does this matter?
if (octr->NumDrivers == 7) mode = 8; //default 1p arcade
}
#endif
switch(mode)

View File

@ -1,4 +1,7 @@
#include <common.h>
#ifdef USE_RETROFUELED && USE_ONLINE
#include "../AltMods/OnlineCTR/global.h"
#endif
// budget: 4624
// curr: 4380
@ -658,7 +661,7 @@ CheckJumpButtons:
actionsFlagSetCopy |= 4;
}
}
#if !defined(USE_RETROFUELED)
if
(
// If you are holding Square
@ -671,6 +674,39 @@ CheckJumpButtons:
// Set Reserves to zero
driver->reserves = 0;
}
#else
// Assume you're holding cross (X)
u_char assumeCross = 0x10;
// if you are holding square
if (square != 0)
{
#if defined(USE_ONLINE)
int rn = octr->serverRoom;
if (!ROOM_IS_RETRO(rn)) //if not retro mode
goto SKIP_RF;
#endif
// held DOWN or have landing boost
if ((ptrgamepad->buttonsHeldCurrFrame & BTN_DOWN) ||
(driver->jump_LandingBoost))
{
// if not holding cross (X)
if (cross == 0)
{
assumeCross = 0;
}
goto SKIP_RESERVE_RESET;
}
SKIP_RF:
// you're on a turbo pad
if (driver->stepFlagSet & 0x3)
goto SKIP_RESERVE_RESET;
// Set Reserves to zero
driver->reserves = 0;
}
SKIP_RESERVE_RESET:
#endif
// assume normal gas pedal
stickRY = 0x80;
@ -715,9 +751,13 @@ CheckJumpButtons:
square = 0;
}
#if !defined(USE_RETROFUELED)
// Assume you're holding Cross, because
// you have Reserves and you aren't slowing down
cross = 0x10;
#else
cross = assumeCross;
#endif
}

View File

@ -18,8 +18,9 @@ void DECOMP_VehPickupItem_ShootNow(struct Driver* d, int weaponID, int flags)
struct GameTracker* gGT = sdata->gGT;
int modelID;
#if 0 && defined(USE_ONLINE)
if(d->driverID == 0)
#if /*0 &&*/ defined(USE_ONLINE)
int rn = octr->serverRoom;
if(ROOM_IS_ITEMS(rn) && d->driverID == 0) //if in item lobby and is ourself
{
octr->Shoot[0].boolJuiced = 0;
if(d->numWumpas >= 10) octr->Shoot[0].boolJuiced = 1;

View File

@ -8911,6 +8911,7 @@ struct Data data =
.RNG_itemSetRace1 =
{
#ifdef USE_ONLINE
// 8/20 potion
// 8/20 tnt
// 2/20 bomb
@ -8921,10 +8922,47 @@ struct Data data =
1, 1,
0,
6,
#else
// 8/20 potion
// 8/20 tnt
// 2/20 bomb
// 1/20 turbo
// 1/20 shield
4, 4, 4, 4, 4, 4, 4, 4,
3, 3, 3, 3, 3, 3, 3, 3,
1, 1,
0,
6,
#endif
},
.RNG_itemSetRace2 =
{
#ifdef USE_ONLINE
// 11/52 potion
// 9/52 tnt
// 5/52 bomb
// 4/52 bomb x3
// 7/52 turbo
// 2/52 shield
// 3/52 missile
// 2/52 missile x3
// 1/52 warpball
// 7/52 mask
// 1/52 n tropy clock
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
3, 3, 3, 3, 3, 3, 3, 3, 3,
1, 1, 1, 1, 1,
10, 10, 10, 10,
0, 0, 0, 0, 0, 0, 0,
6, 6,
2, 2, 2,
11, 11,
9,
7, 7, 7, 7, 7, 7, 7,
8,
#else
// 10/52 potion
// 8/52 tnt
// 5/52 bomb
@ -8948,16 +8986,66 @@ struct Data data =
9, 9, 9, 9, 9,
7, 7, 7, 7, 7, 7, 7,
8,
#endif
},
.RNG_itemSetRace3 =
{
#ifdef USE_ONLINE
// 1/20 potion
// 1/20 tnt
// 1/20 bomb x1
// 3/20 bomb x3
// 2/20 turbo
// 1/20 shield
// 3/20 missile x1
// 1/20 missile x3
// 1/20 warpball
// 5/20 mask
// 1/20 clock
4, 3, 1, 10, 10, 10, 0, 0, 6, 2, 2, 2, 11, 9, 7, 7, 7, 7, 7, 8,
#else
// 1/20 potion
// 1/20 tnt
// 1/20 bomb x1
// 2/20 bomb x3
// 2/20 turbo
// 1/20 shield
// 2/20 missile x1
// 1/20 missile x3
// 3/20 warpball
// 5/20 mask
// 1/20 clock
4, 3, 1, 10, 10, 0, 0, 6, 2, 2, 11, 9, 9, 9, 7, 7, 7, 7, 7, 8,
#endif
},
.RNG_itemSetRace4 =
{
#ifdef USE_ONLINE
// 2/20 turbo
// 1/20 shield
// 2/20 missile x1
// 3/20 missile x3
// 1/20 bomb x3
// 2/20 warpball
// 8/20 mask
// 1/20 clock
0, 0, 6, 2, 2, 11, 11, 11, 10, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 8,
#else
// 2/20 turbo (one zero at the start, and one at the end)
// 1/20 shield
// 1/20 missile x1
// 1/20 missile x3
// 5/20 warpball
// 8/20 mask
// 2/20 clock
0, 6, 2, 11, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 0,
#endif
},
.RNG_itemSetBossrace =

2
externals/PsyCross vendored

@ -1 +1 @@
Subproject commit 0ce306d6c32412986037e7e5e1dbdc1bf72e066a
Subproject commit b086bba2a20a6208e8f5272b38357717de01eb17

2
externals/SDL vendored

@ -1 +1 @@
Subproject commit f461d91cd265d7b9a44b4d472b1df0c0ad2855a0
Subproject commit 88125e4d2dd6dea060979fe4afe6b4d700e04cf4

@ -1 +1 @@
Subproject commit c8bcaf8a913f1ac087b076488e0cbbc272cccd19
Subproject commit cee838e335ba514df65fd7344f55fe8e525848c5

2
externals/enet vendored

@ -1 +1 @@
Subproject commit 2662c0de09e36f2a2030ccc2c528a3e4c9e8138a
Subproject commit 1e80a78f481cb2d2e4d9a0e2718b91995f2de51c

2
externals/imgui vendored

@ -1 +1 @@
Subproject commit c3c90b49e0342c5c9772f4fa26de39dd44ea831a
Subproject commit 864a2bf6b824f9c1329d8493386208d4b0fd311c

@ -1 +1 @@
Subproject commit d3875f333fb6abe2f39d82caca329414871ae53b
Subproject commit 56c46020580162c1a7bf3c577c1a550c94576c41

View File

@ -16,7 +16,7 @@
//#define USE_BIGQUEUE // Requires RAMEX: Extended loading queue
//#define USE_HIGH1P // Requires BIGQUEUE: All high model drivers
//#define USE_RANDOM // Requires HIGH1P: Character Randomizer
//#define USE_ONLINE // Requires HIGH1P: Online Multiplayer
#define USE_ONLINE // Requires HIGH1P: Online Multiplayer
//#define USE_HIGHMP // Requires RAMEX: Multiplayer Maxed mod
//#define USE_VR // Virtual Reality
@ -27,7 +27,10 @@
#define USE_16BY9
#define USE_RAMEX
#define USE_BIGQUEUE
//#define PINE_DEBUG //enable this for logging of CurrState change on game and client.
#define USE_HIGH1P
//note: if you disable this, you'll need to fix anything related to the `ROOM_...` defines in global.h
#define USE_RETROFUELED //enabled only in certain rooms.
#endif
#ifdef USE_60FPS

File diff suppressed because it is too large Load Diff

View File

@ -157,14 +157,9 @@
<ClCompile Include="..\..\..\..\..\externals\enet\unix.c" />
<ClCompile Include="..\..\..\..\..\externals\enet\win32.c" />
<ClCompile Include="CL_main.cpp" />
<ClCompile Include="DeferredMem.cpp" />
<ClCompile Include="Util.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\..\..\decompile\General\AltMods\OnlineCTR\global.h" />
<ClInclude Include="DeferredMem.h" />
<ClInclude Include="DSPINE.h" />
<ClInclude Include="Util.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@ -48,25 +48,10 @@
<ClCompile Include="..\..\..\..\..\externals\enet\win32.c">
<Filter>Source Files\enet</Filter>
</ClCompile>
<ClCompile Include="DeferredMem.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Util.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\..\..\decompile\General\AltMods\OnlineCTR\global.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="DSPINE.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="DeferredMem.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Util.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -1,193 +0,0 @@
#pragma once
//Based on my (TheUbMunster)'s testing, this description of the """api"""
//from the comment in https://github.com/stenzek/duckstation/blob/master/src/core/pine_server.cpp
//is straight up not correct (at time of writing)
// example IPC messages: MsgRead/Write
// refer to the client doc for more info on the format
// IPC Message event (1 byte)
// | Memory address (4 byte)
// | | argument (VLE)
// | | |
// format: XX YY YY YY YY ZZ ZZ ZZ ZZ
// reply code: 00 = OK, FF = NOT OK
// | return value (VLE)
// | |
// reply: XX ZZ ZZ ZZ ZZ
//The PR https://github.com/stenzek/duckstation/pull/3219 seems to have some slightly more accurate
//information, that said, these comments reflect my observations.
//I believe this is how it's actually formatted (at least for DSPINEMsgRead64):
// Packet size (the literal value 9 occupies these 4 bytes)
// | IPC Message Event (1 byte)
// | | Address (4 bytes)
// | | |
// | | |
// format: XX XX XX XX YY ZZ ZZ ZZ ZZ
// Packet size (the literal value 13 occupies this 1 byte).
// | Reply code (00 if success, FF if fail)
// | | Memory at that address
// | | |
// reply: XX XX XX XX YY ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ
//I believe this is how it's actually formatted (at least for DSPINEMsgWrite64):
// Packet size (the literal value 17 occupies these 4 bytes)
// | IPC Message Event (1 byte)
// | | Address (4 bytes)
// | | | The value to write to memory
// | | | |
// | | | |
// format: VV VV VV VV WW XX XX XX XX YY YY YY YY YY YY YY YY
// Packet size (the literal value 5 occupies this 1 byte).
// | Reply code (00 if success, FF if fail)
// | |
// reply: XX XX XX XX YY
#define DSPINEMsgRead8 0 /**< Read 8 bit value to memory. */
#define DSPINEMsgRead16 1 /**< Read 16 bit value to memory. */
#define DSPINEMsgRead32 2 /**< Read 32 bit value to memory. */
#define DSPINEMsgRead64 3 /**< Read 64 bit value to memory. */
#define DSPINEMsgWrite8 4 /**< Write 8 bit value to memory. */
#define DSPINEMsgWrite16 5 /**< Write 16 bit value to memory. */
#define DSPINEMsgWrite32 6 /**< Write 32 bit value to memory. */
#define DSPINEMsgWrite64 7 /**< Write 64 bit value to memory. */
#define DSPINEMsgVersion 8 /**< Returns PCSX2 version. */
#define DSPINEMsgSaveState 9 /**< Saves a savestate. */
#define DSPINEMsgLoadState 0xA /**< Loads a savestate. */
#define DSPINEMsgTitle 0xB /**< Returns the game title. */
#define DSPINEMsgID 0xC /**< Returns the game ID. */
#define DSPINEMsgUUID 0xD /**< Returns the game UUID. */
#define DSPINEMsgGameVersion 0xE /**< Returns the game verion. */
#define DSPINEMsgStatus 0xF /**< Returns the emulator status. */
#define DSPINEMsgUnimplemented 0xFF /**< Unimplemented IPC message. */
// Only struct impl for Read64, Write8/16/32/64, no more were needed at time of writing.
// if future maintainers need more, make sure to update the DSPINESend/Recv unions at the bottom of this file.
#pragma pack(push, 1) //api requires packed, this pragma works for VC++ and GCC, maybe some others
struct DSPINERead64Send
{
unsigned int packetSize = sizeof(*this); //should be sizeof(DSPINERead64Send) (9)
unsigned char DSPINEMsgIPC = DSPINEMsgRead64; //one of the "DSPINEMsg" defines in DSPINE.h
unsigned int address;
};
struct DSPINERead64Recv
{
unsigned int packetSize = sizeof(*this); //should be sizeof(DSPINERead64Recv) (13)
unsigned char DSPINEMsgReplyCode; //0x00 if success, 0xFF if failure
union
{
unsigned long long whole;
char bytes[8];
} data;
};
struct DSPINEWrite8Send
{
unsigned int packetSize = sizeof(*this); //should be sizeof(DSPINEWrite8Send) (10)
unsigned char DSPINEMsgIPC = DSPINEMsgWrite8; //one of the "DSPINEMsg" defines in DSPINE.h
unsigned int address;
union
{
unsigned char whole;
char bytes[1];
} data;
};
struct DSPINEWrite8Recv
{
unsigned int packetSize = sizeof(*this); //should be sizeof(DSPINEWrite8Recv) (5)
unsigned char DSPINEMsgReplyCode; //0x00 if success, 0xFF if failure
};
struct DSPINEWrite16Send
{
unsigned int packetSize = sizeof(*this); //should be sizeof(DSPINEWrite16Send) (11)
unsigned char DSPINEMsgIPC = DSPINEMsgWrite16; //one of the "DSPINEMsg" defines in DSPINE.h
unsigned int address;
union
{
unsigned short whole;
char bytes[2];
} data;
};
struct DSPINEWrite16Recv
{
unsigned int packetSize = sizeof(*this); //should be sizeof(DSPINEWrite16Recv) (5)
unsigned char DSPINEMsgReplyCode; //0x00 if success, 0xFF if failure
};
struct DSPINEWrite32Send
{
unsigned int packetSize = sizeof(*this); //should be sizeof(DSPINEWrite32Send) (13)
unsigned char DSPINEMsgIPC = DSPINEMsgWrite32; //one of the "DSPINEMsg" defines in DSPINE.h
unsigned int address;
union
{
unsigned int whole;
char bytes[4];
} data;
};
struct DSPINEWrite32Recv
{
unsigned int packetSize = sizeof(*this); //should be sizeof(DSPINEWrite32Recv) (5)
unsigned char DSPINEMsgReplyCode; //0x00 if success, 0xFF if failure
};
struct DSPINEWrite64Send
{
/*static const unsigned int recvPacketSize = sizeof(DSPINEWrite64Recv);*/
unsigned int packetSize = sizeof(*this); //should be sizeof(DSPINEWrite64Send) (17)
unsigned char DSPINEMsgIPC = DSPINEMsgWrite64; //one of the "DSPINEMsg" defines in DSPINE.h
unsigned int address;
union
{
unsigned long long whole;
char bytes[8];
} data;
};
struct DSPINEWrite64Recv
{
unsigned int packetSize = sizeof(*this); //should be sizeof(DSPINEWrite64Recv) (5)
unsigned char DSPINEMsgReplyCode; //0x00 if success, 0xFF if failure
};
//unions for send/recv
union DSPINESend
{
struct SharedHeader
{
unsigned int packetSize;
unsigned char DSPINEMsgIPC;
unsigned int address;
} shared_header;
DSPINERead64Send read64;
DSPINEWrite8Send write8;
DSPINEWrite16Send write16;
DSPINEWrite32Send write32;
DSPINEWrite64Send write64;
};
union DSPINERecv
{
struct SharedHeader
{
unsigned int packetSize;
unsigned char DSPINEMsgReplyCode;
} shared_header;
DSPINERead64Recv read64;
DSPINEWrite8Recv write8;
DSPINEWrite16Recv write16;
DSPINEWrite32Recv write32;
DSPINEWrite64Recv write64;
};
#pragma pack(pop)

View File

@ -1,471 +0,0 @@
#ifdef _WIN64 //windows
#include <WinSock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")
#else //assume posix
#error todo...
//todo:
//include whatever headers we need for posix sockets & posix sockets implementations.
#endif
#include "DeferredMem.h"
#include "Util.h"
#include <atomic>
#include <thread>
#include <semaphore>
#include <condition_variable>
#include <map>
#include <vector>
void recvThread();
typedef unsigned long long internalPineApiID;
internalPineApiID pineSend(DSPINESend sendObj);
void pineRecv();
bool isPineDataPresent(pineApiID id);
#if _WIN64 //windows
SOCKET dspineSocket;
#else //assume posix
#error todo...
//todo:
//declare a variable of whatever type a posix socket is.
#endif
std::thread recvWorker;
std::mutex pineObjsMutex;
/// <summary>
/// Initializes the deferred memory model. Returns false if failed (e.g., TCP socket not connected).
/// If it failed, it needs to be called again until true before the deferred memory model is truly initialized.
/// </summary>
bool defMemInit()
{
#if _WIN64 //windows
dspineSocket = initSocket();
#else //assume posix
#error todo...
//todo:
//call posix version of initSocket() and assign to the posix dspineSocket variable.
#endif
if (dspineSocket != INVALID_SOCKET)
{
recvWorker = std::thread{ recvThread };
return true;
}
else
return false;
}
bool socketValid()
{
#if _WIN64 //windows
return dspineSocket != INVALID_SOCKET;
#else //assume posix
#error todo...
#endif
}
#if _WIN64 //windows
SOCKET initSocket() //every call to initSocket should be bookmatched by a call to uninitSocket.
{
//https://learn.microsoft.com/en-us/windows/win32/winsock/creating-a-basic-winsock-application
WSADATA wsadata;
int ires;
ires = WSAStartup(MAKEWORD(2, 2), &wsadata);
if (ires != 0)
{
printf("WSAStartup failed with code: %d\n", ires);
return NULL;
}
struct addrinfo* result = NULL, * ptr = NULL, hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
ires = getaddrinfo("127.0.0.1", "28011", &hints, &result); //DS PINE
if (ires != 0)
{
printf("getaddrinfo failed with code: %d\n", ires);
WSACleanup();
return NULL;
}
SOCKET sock = INVALID_SOCKET;
ptr = result;
sock = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (sock == INVALID_SOCKET)
{
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return NULL;
}
ires = connect(sock, ptr->ai_addr, (int)ptr->ai_addrlen);
if (ires == SOCKET_ERROR)
{
closesocket(sock);
printf("Error trying to connect socket: %ld\n", WSAGetLastError());
sock = INVALID_SOCKET;
}
freeaddrinfo(result);
if (sock == INVALID_SOCKET)
{
printf("Unable to connect to DuckStation PINE!\n");
closesocket(sock);
WSACleanup();
return NULL;
}
else
printf("DuckStation PINE socket acquired.\n");
u_long mode = 1;
ires = ioctlsocket(sock, FIONBIO, &mode); //make the socket non-blocking
if (ires == SOCKET_ERROR)
{
printf("Unable to put the socket into non-blocking mode.\n");
closesocket(sock);
WSACleanup();
return NULL;
}
int enable = 1;
ires = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&enable, sizeof(int));
if (ires != 0)
{
printf("Unable to enable TCP_NODELAY (disables nagle's algorithm).\n");
closesocket(sock);
WSACleanup();
return NULL;
}
return sock;
}
void uninitSocket(SOCKET* socket) //should be preceded by a call to initSocket
{
if (*socket != INVALID_SOCKET)
{
WSACleanup();
}
}
#else //assume posix
#error todo...
//todo:
//implement a posix version of initSocket
//it should:
// 1. create a socket in family AF_INET
// 2. type of SOCK_STREAM (if that granularity exists)
// 3. of protocol TCP
// 4. for ip address 127.0.0.1/localhost and port 28011
// 5. if applicable during socket construction, specify that send()/recv() are *non-blocking*
// 6. ensure that it's connected
// 7. if anything fails, log why, and clean up.
// 8. return the created socket if creation succeeded.
//implement a posix version of uninitSocket
//it should:
// 1. close connection (if open), clean up & invalidate a socket.
#endif
void recvThread()
{
while (true)
{
//recvCond.notify_all();
//std::unique_lock<std::mutex> um{ recvMutex };
//recvCond.wait(um, [] { return outstandingReads > 0; });
pineRecv();
}
}
internalPineApiID pineSendsCount = 0, pineRecvsCount = 0;
/// <summary>
/// Upon a call to pineSend(), an entry in this collection will be made, where the value contains
/// just the "send" portion of the DSPINESendRecvPair if the bool is false, and the value contains
/// both portions of the DSPINESendRecvPair if the bool is true.
/// </summary>
std::map<internalPineApiID, std::pair<DSPINESendRecvPair, bool>> pineObjs{};
internalPineApiID pineSend(DSPINESend sendObj)
{ //could be on another thread, but since tcp send is non-blocking it doesn't really matter.
//tcp send
#if _WIN64 //windows
//critical region (syncronize access pls)
{
//need to add it first because the second we send (right after this) the recv thread might try
//to add it *before* we've even made the entry for it, which would be bad.
std::lock_guard<std::mutex> um{ pineObjsMutex };
pineObjs.insert(std::pair<internalPineApiID, std::pair<DSPINESendRecvPair, bool>>{ pineSendsCount, std::pair<DSPINESendRecvPair, bool>{ DSPINESendRecvPair{ sendObj, DSPINERecv{} }, false } });
}
//end critical region
int res = send(dspineSocket, (const char*)&sendObj, sendObj.shared_header.packetSize, 0);
if (res != sendObj.shared_header.packetSize)
{
printf("send() failed!\n");
if (res == SOCKET_ERROR)
exit_execv(6);
else
exit_execv(7); //partial send???
}
#else //assume posix
#error todo...
//posix non-blocking send
#endif
return pineSendsCount++;
}
std::condition_variable waitPineDataCV;
void pineRecv()
{ //on another thread
DSPINERecv recvData{};
#if _WIN64 //windows
WSAPOLLFD fdarr = { 0 };
fdarr.fd = dspineSocket;
fdarr.events = POLLRDNORM;
WSAPoll(&fdarr, 1, -1); //block until something is waiting in tcp buffer.
int recvLen = recv(dspineSocket, (char*)&recvData, sizeof(DSPINERecv::SharedHeader), 0);
if (recvLen == sizeof(DSPINERecv::SharedHeader) &&
//recvData.shared_header.packetSize == /*whatever size this recv is meant to be*/ &&
recvData.shared_header.DSPINEMsgReplyCode == 0)
{ //very good
}
else
{
if (recvLen < sizeof(DSPINERecv::SharedHeader)) //todo: make consumer buffer for this
printf("recv returned less than required buffer length (?packet fragmentation?) "); //partial recv could be solved by coroutine
printf("recv failed: %d\n", WSAGetLastError());
exit_execv(5); //could be caused by many things.
}
unsigned int remainingSize = recvData.shared_header.packetSize - sizeof(DSPINERecv::SharedHeader);
if (remainingSize != 0)
{
//no need to poll bc the first 5 bytes of this packet (which have already been parsed) and whatever remains
//(regardless of packet size) should never have been fragmented (hopefully!)
recvLen = recv(dspineSocket, ((char*)&recvData) + sizeof(DSPINERecv::SharedHeader), remainingSize, 0);
if (recvLen == remainingSize)
{ //very good
}
else
{
if (recvLen < remainingSize) //todo: make consumer buffer for this
printf("recv returned less than required buffer length (?packet fragmentation?) "); //partial recv could be solved by coroutine
else
printf("recv returned ?*MORE*? than the required buffer length (corrupted stack)??? ");
printf("recv failed: %d\n", WSAGetLastError());
exit_execv(5); //could be caused by many things.
}
}
#else //assume posix
#error todo...
//todo:
//this should poll/select for a recv event
//then recv of (maximum) length recvBufLen
//ensure that the length that was recvd was indeed recvBufLen
//ensure that recieveBuffer[0] == recvBufLen (PINE PROTOCOL)
//ensure that recieveBuffer[4] == 0 (PINE PROTOCOL)
//if any "ensures" fail, then we have reached an unrecoverable error,
//assume that the socket connection is bad and reset and/or close the client.
#endif
//critical region (syncronize access pls)
{
std::lock_guard<std::mutex> um{ pineObjsMutex };
auto& e = pineObjs.at(pineRecvsCount);
e.first.recvData = recvData;
e.second = true;
pineRecvsCount++;
waitPineDataCV.notify_all();
}
//end critical region
}
pineApiID pineApiRequestCount = 0;
/// <summary>
/// A single "pineApiID" correlates to a group of "internalPineApiID". If the boolean is true,
/// then the entry is active, if it's false, then it's marked for the garbage collector.
/// </summary>
std::map<pineApiID, std::pair<std::vector<internalPineApiID>, bool>> pineApiRequests{};
void markPineDataForGC(pineApiID id)
{
auto& dat = pineApiRequests.at(id).second = false;
}
void GCDeadPineData()
{
std::vector<pineApiID> toRemove{};
std::vector<internalPineApiID> intToRemove{};
for (auto& e : pineApiRequests)
{
if (!e.second.second && isPineDataPresent(e.first))
{
toRemove.push_back(e.first);
for (auto& iid : e.second.first)
{
intToRemove.push_back(iid);
}
}
}
//remove eligible data.
for (auto& id : toRemove)
{
pineApiRequests.erase(id);
}
//critical region (syncronize access pls)
std::lock_guard<std::mutex> um{ pineObjsMutex };
{
for (auto& iid : intToRemove)
{
pineObjs.erase(iid);
}
}
//end critical region
}
/// <summary>
/// Checks if all the data correlated with a single pineApiID has been fully recieved.
///
/// !!!WARNING!!! Callers of this function must acquire the "pineObjsMutex" before calling.
/// This function is internal, and should not be called outside the API.
/// </summary>
bool isPineDataPresent(pineApiID id)
{
bool isAllPresent = true;
auto& dat = pineApiRequests.at(id).first;
//critical region (syncronize access pls)
{
//CALLERS MUST ACQUIRE THIS MUTEX BEFORE CALLING!
//std::lock_guard<std::mutex> um{ pineObjsMutex };
for (size_t i = 0; i < dat.size(); i++)
{
isAllPresent &= pineObjs.at(dat[i]).second; //this bool is only true when it's been recvd
}
}
//end critical region
return isAllPresent;
}
void waitUntilPineDataPresent(pineApiID id)
{
std::unique_lock<std::mutex> ul{ pineObjsMutex };
//its possible that after we've acquired the mutex, the data arrives, but it arrives
//BEFORE the .wait call, (i.e., waitPineDataCV.notify_all() gets called before .wait()), leading to deadlock.
if (!isPineDataPresent(id))
waitPineDataCV.wait(ul, [id] { return isPineDataPresent(id); });
}
std::vector<DSPINESendRecvPair> getPineDataSegment(pineApiID id)
{
std::vector<DSPINESendRecvPair> segment{};
auto& dat = pineApiRequests.at(id).first;
//critical region (syncronize access pls)
{
std::lock_guard<std::mutex> um{ pineObjsMutex };
for (size_t i = 0; i < dat.size(); i++)
{
DSPINESendRecvPair obj = pineObjs.at(dat[i]).first;
segment.push_back(obj);
}
}
//end critical region
return segment;
}
pineApiID send_readMemorySegment(unsigned int addr, size_t len)
{
if (len == 0)
exit_execv(10); //this function only works if you attempt to read a non-zero length of memory.
std::vector<internalPineApiID> iids{};
//size_t roundedUpLen = len + ((len % 8 != 0) ? (8 - (len % 8)) : 0);
size_t roundedUpLen = ((len & 0x7) != 0) ? (len & ~0x7) + 8 : len; //should be identical to above
for (size_t i = 0; i < roundedUpLen; i += 8)
{
DSPINESend sendObj{};
//TODO: verify that this assigns members like packetsize etc. correctly automatically.
sendObj.read64 = DSPINERead64Send{};
sendObj.read64.address = addr + i;
iids.push_back(pineSend(sendObj));
}
pineApiRequests.insert(std::pair<pineApiID, std::pair<std::vector<internalPineApiID>, bool>>{pineApiRequestCount, std::pair<std::vector<internalPineApiID>, bool>{ iids, true }});
return pineApiRequestCount++;
}
pineApiID send_writeMemorySegment(unsigned int addr, size_t len, char* buf, char* originalBuf)
{
if (len == 0)
exit_execv(11); //this function only works if you attempt to write a non-zero length of memory.
std::vector<internalPineApiID> iids{};
auto dispatchContig = [&iids](unsigned int address, size_t length, char* buffer)
{
pineApiID firstSendID = 0;
size_t sendCount = 0; //may not necessarily result in whole / 8
size_t whole = length - (length % 8);
size_t rem = length - whole; //whatever is left over.
//size_t i = 0;
for (size_t i = 0; i < whole; i += 8)
{
DSPINESend sendObj{};
//TODO: verify that this assigns members like packetsize etc. correctly automatically.
sendObj.write64 = DSPINEWrite64Send{};
sendObj.write64.address = address + i;
memcpy(sendObj.write64.data.bytes, buffer + i, 8);
iids.push_back(pineSend(sendObj));
}
//note: rem is [0-7] inclusive
//unsigned int offsetaddr = addr + whole;
unsigned int offset = 0;
if ((rem & 4) != 0) //we need a 4
{
DSPINESend sendObj{};
sendObj.write32 = DSPINEWrite32Send{};
sendObj.write32.address = address + whole + offset;
memcpy(sendObj.write32.data.bytes, buffer + whole + offset, 4);
iids.push_back(pineSend(sendObj));
offset += 4;
}
if ((rem & 2) != 0) //we need a 2
{
DSPINESend sendObj{};
sendObj.write16 = DSPINEWrite16Send{};
sendObj.write16.address = address + whole + offset;
memcpy(sendObj.write16.data.bytes, buffer + whole + offset, 2);
iids.push_back(pineSend(sendObj));
offset += 2;
}
if ((rem & 1) != 0) //we need a 2
{
DSPINESend sendObj{};
sendObj.write8 = DSPINEWrite8Send{};
sendObj.write8.address = address + whole + offset;
memcpy(sendObj.write8.data.bytes, buffer + whole + offset, 1);
iids.push_back(pineSend(sendObj));
offset += 1;
}
};
if (originalBuf == nullptr)
{
dispatchContig(addr, len, buf);
}
else
{
pineApiID firstSendID = 0;
size_t sendCount = 0;
long mismatchStart = -1, mismatchLength = 1;
for (size_t i = 0; i < len; i++)
{
if (mismatchStart != -1 && buf[i] != originalBuf[i])
mismatchLength++; //we already have a mismatch, make it longer
if (mismatchStart == -1 && buf[i] != originalBuf[i])
mismatchStart = i; //start a new mismatch
if (mismatchStart != -1 && buf[i] == originalBuf[i])
{ //end of the mismatch, now dispatch
dispatchContig(addr + mismatchStart, mismatchLength, buf + mismatchStart);
mismatchStart = -1; mismatchLength = 1; //at the very end
}
}
if (mismatchStart != -1)
dispatchContig(addr + mismatchStart, mismatchLength, buf + mismatchStart);
}
pineApiRequests.insert(std::pair<pineApiID, std::pair<std::vector<internalPineApiID>, bool>>{pineApiRequestCount, std::pair<std::vector<internalPineApiID>, bool>{ iids, true }});
return pineApiRequestCount++;
}

View File

@ -1,240 +0,0 @@
#pragma once
#ifndef DEF_MEM //include guard
#define DEF_MEM
#ifdef _WIN64 //windows
#include <WinSock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#else //assume posix
#error todo...
//todo:
//include whatever headers we need for posix socket types.
#endif
#include "DSPINE.h"
#include "Util.h"
#include <memory>
#include <functional>
#include <vector>
//TODO: make as few of these public as possible.
bool defMemInit();
bool socketValid();
//void readMemorySegment(unsigned int addr, size_t len, char* buf);
//void writeMemorySegment(unsigned int addr, size_t len, char* buf/*, bool blocking = false*/);
#if _WIN64 //windows
SOCKET initSocket();
void uninitSocket(SOCKET* socket);
#else //assume posix
#error todo...
//todo:
//declare functions initSocket() and uninitSocket() using the equivalent posix data type for SOCKET
//Note: the remainder of this file likely shouldn't need to be modified for the sake of posix sockets.
#endif
struct DSPINESendRecvPair
{
DSPINESend sendData;
DSPINERecv recvData;
DSPINESendRecvPair(DSPINESend s, DSPINERecv r) : sendData(s), recvData(r) {}
};
// this function needs to be called repeatedly, occasionally to clean up dead pine data.
void GCDeadPineData();
typedef unsigned long long pineApiID;
//never call any of these 5 functions manually, only for use by ps1ptr.
void markPineDataForGC(pineApiID id);
void waitUntilPineDataPresent(pineApiID id);
std::vector<DSPINESendRecvPair> getPineDataSegment(pineApiID id);
pineApiID send_readMemorySegment(unsigned int addr, size_t len);
pineApiID send_writeMemorySegment(unsigned int addr, size_t len, char* buf, char* originalBuf = nullptr);
//https://isocpp.org/wiki/faq/templates#templates-defn-vs-decl
template<typename T>
class ps1ptr
{
private:
enum pineState
{
none,
reading,
writing,
};
template<typename U> friend class ps1ptr;
public:
typedef std::shared_ptr<T> ptrtype;
private:
unsigned int address;
/*bool volat;*/
bool didPrefetch;
char* buf;
char* originalBuf;
std::shared_ptr<void> parentIfPresent;
ptrtype bufferedVal; //implicit dtor will delete this, which deletes the buf and originalBuf
pineState pState = none;
pineApiID outstandingAPIID;
public:
//for static initialization only, do not actually ever call this.
ps1ptr() { }
/// <summary>
/// Reads the specified address from PS1 (emulator RAM), copies it, and creates a shared_ptr.
///
/// The object itself is only a copy of emulator RAM, so to commit changes, use commit().
/// If changes have been made to emulator RAM, use refresh() to re-fetch the underlying memory.
///
/// When this object falls out of scope, any changes made will *not* be automatically committed.
/// Do it yourself.
/// </summary>
/// <param name="addr">The address of PS1 memory to represent</param>
/// <param name="prefetch">If false, this ps1ptr will not prefetch the contents of that memory upon construction.
/// This is useful if you plan on completely overwriting this portion of memory, saving latency.
/// However, an object in this state will always commit it's entire buffer to ps1 memory every
/// time it is commited, so make sure *all* of the memory this ps1ptr represents is set as you intend.</param>
ps1ptr(unsigned int addr, bool prefetch = true) : address(addr & 0xffffff), didPrefetch(prefetch)
{
buf = new char[sizeof(T)];
originalBuf = new char[sizeof(T)];
parentIfPresent = nullptr;
bufferedVal = ptrtype((T*)&buf[0], [=](T* val) { delete[] buf; delete[] originalBuf; }); //when the shared_ptr dies, free the char* off the heap.
if (prefetch)
blockingRead();
else
{
memset(buf, 0, sizeof(T));
memcpy(originalBuf, buf, sizeof(T));
}
}
//TODO: this needs work
/// <summary>
/// This ctor is for if you want to make a ps1ptr that represents just a portion of an existing ps1ptr.
/// e.g., if you intend to have very high usage of a submember of some other ps1ptr like octr, this can lighten the
/// load on TCP/PINE. Currently needs more testing, but this could allow for higher performance.
/// </summary>
template<typename U>
ps1ptr(ps1ptr<U> parentPtr, size_t offset) : parentIfPresent(parentPtr.bufferedVal), address((parentPtr.address + offset) & 0xffffff)
{
buf = parentPtr.buf + offset;
originalBuf = parentPtr.originalBuf + offset;
bufferedVal = std::shared_ptr<T>((T*)&buf[0], [=](T* val) { /* do nothing, underlying mem is destroyed with parent */ });
didPrefetch = true; //functionally desirable in this case
}
//TODO: this needs work
template<typename U>
ps1ptr<U> createSubmemberPtr(size_t offset)
{
return ps1ptr<U>{*this, offset};
}
void blockingRead()
{
startRead();
waitRead();
}
void startRead()
{
if (pState == reading) //this was waitRead();
markPineDataForGC(outstandingAPIID);
else if (pState == writing) //this was waitWrite();
markPineDataForGC(outstandingAPIID);
//if (pState != none)
// exit_execv(69); //todo abort bad
outstandingAPIID = send_readMemorySegment(address, sizeof(T));
pState = reading; //set as reading regardless of what it was before
}
void waitRead()
{
if (pState != reading)
exit_execv(13); //todo abort bad
waitUntilPineDataPresent(outstandingAPIID);
std::vector<DSPINESendRecvPair> pineData = getPineDataSegment(outstandingAPIID);
//reads should be of the form: 8? 8? 8? 8? ... (excess is ignored if not a multiple of 8)
for (size_t i = 0; i < pineData.size(); i++)
{
DSPINESendRecvPair dat = pineData.at(i);
size_t bufInd = dat.sendData.shared_header.address - address;
size_t len = ((sizeof(T) - bufInd) >= 8) ? 8 : sizeof(T) - bufInd;
memcpy(buf + bufInd, dat.recvData.read64.data.bytes, len);
}
markPineDataForGC(outstandingAPIID);
memcpy(originalBuf, buf, sizeof(T));
didPrefetch = true; //if we've read, we can treat this object (from this point on) as if it was prefetched.
//at the very end
pState = none;
}
void blockingWrite()
{
startWrite();
waitWrite();
}
void startWrite()
{
if (pState == reading)
waitRead();
else if (pState == writing) //this was waitWrite();
markPineDataForGC(outstandingAPIID);
//if (pState != none)
// exit_execv(69); //todo abort bad
outstandingAPIID = send_writeMemorySegment(address, sizeof(T), buf, didPrefetch ? originalBuf : nullptr);
memcpy(originalBuf, buf, sizeof(T));
didPrefetch = true; //if we've written, we can treat this object (from this point on) as if it was prefetched, because it now *is* the canonical memory state
pState = writing; //set as writing regardless of what it was before
}
void waitWrite()
{
if (pState != writing)
exit_execv(12); //todo abort bad
waitUntilPineDataPresent(outstandingAPIID);
//currently nothing needs to be done with pineData
//std::vector<DSPINESendRecvPair> pineData = getPineDataSegment(outstandingAPIID);
markPineDataForGC(outstandingAPIID); //integrity & proper sizes are verified by the api functions
//at the very end
pState = none;
}
ptrtype get()
{
if (pState == writing)
{
//was waitWrite();
waitUntilPineDataPresent(outstandingAPIID);
pState = none; //no longer considered writing
}
if (pState != none)
exit_execv(14); //todo abort bad
return bufferedVal;
}
unsigned int get_address()
{
return address;
}
~ps1ptr()
{
if (pState != none)
markPineDataForGC(outstandingAPIID);
}
};
class ps1mem
{
private:
unsigned int address;
public:
ps1mem() { } //for static init only, do not actually use this.
ps1mem(unsigned int addr) : address(addr) { }
/// <summary>
/// Creates a ps1ptr of the specified address, offset by the address of this ps1mem.
/// </summary>
template<typename T>
ps1ptr<T> at(unsigned int addr, bool prefetch = true)
{
return ps1ptr<T>(addr + address, prefetch);
}
};
#endif //DEF_MEM

View File

@ -1,40 +0,0 @@
#include <windows.h>
#include "Util.h"
#include <stdio.h>
#include <stdlib.h>
#include <thread>
const char* exitReasons[] =
{
"[unused exit reason]", //1
"[unused exit reason]", //2
"failed to create an enet client host", //3 g
"no available peers for initiating an ENet connection", //4 g
"recv in pineRecv failed", //5 g
"pineSend socket error", //6 g
"pineSend partial send", //7 g
"[unused exit reason]", //8
"[unused exit reason]", //9
"send_readMemorySegment attempt to send 0 bytes", //10 g
"send_writeMemorySegment attempt to send 0 bytes", //11 g
"attempt to ps1ptr.waitWrite() when not writing", //12 g
"attempt to ps1ptr.waitRead() when not reading", //13 g
"attempt to ps1ptr.get() when PINE busy", //14 g
};
extern char* progName;
void exit_execv(int code)
{
bool codeHasExitReason = code >= sizeof(exitReasons) / sizeof(exitReasons[0]);
const char* newArgv[] = { progName, codeHasExitReason ? exitReasons[code - 1] : "unknown exit reason", nullptr};
if (_execv(progName, newArgv) == -1)
{
if (codeHasExitReason)
printf("exit(%d) - unknown exit reason\n", code);
else
printf("exit(%d) - %s\n", code, exitReasons[code - 1]);
exit(code);
}
}

View File

@ -1,6 +0,0 @@
#pragma once //why doesn't this work
const char* exitReasons[];
extern char* progName;
void exit_execv(int code);

View File

@ -449,7 +449,25 @@ void ProcessReceiveEvent(ENetPeer* peer, ENetPacket* packet) {
s->juiced = r->juiced;
s->flags = r->flags;
broadcastToPeersReliable(ri, s, sizeof(struct SG_MessageWeapon));
//this is a potential cheat mitigation:
//I have it commented out for now BC if we ever change the ROOM_... allocations, that also
//requires a server update (lame). Enable this if cheaters using items in non-item rooms becomes
//a problem.
//int rn = 0;
//for (; rn < 16; rn++)
// if (roomInfos + rn == ri)
// break;
//if (
// (rn >= ROOM_ITEMSTART && rn < ROOM_ITEMSTART + ROOM_ITEMLENGTH) ||
// (rn >= ROOM_ITEMRETROSTART && rn < ROOM_ITEMRETROSTART + ROOM_ITEMRETROLENGTH)
// ) //if this room is in item mode.
//{
// broadcastToPeersReliable(ri, s, sizeof(struct SG_MessageWeapon));
//}
broadcastToPeersReliable(ri, s, sizeof(struct SG_MessageWeapon)); //comment this if using the above cheat mitigation
break;
}

View File

@ -174,16 +174,16 @@
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="packages\sdl2.nuget.redist.2.30.3\build\native\sdl2.nuget.redist.targets" Condition="Exists('packages\sdl2.nuget.redist.2.30.3\build\native\sdl2.nuget.redist.targets')" />
<Import Project="packages\sdl2.nuget.2.30.3\build\native\sdl2.nuget.targets" Condition="Exists('packages\sdl2.nuget.2.30.3\build\native\sdl2.nuget.targets')" />
<Import Project="packages\sdl2.nuget.redist.2.30.7\build\native\sdl2.nuget.redist.targets" Condition="Exists('packages\sdl2.nuget.redist.2.30.7\build\native\sdl2.nuget.redist.targets')" />
<Import Project="packages\sdl2.nuget.2.30.7\build\native\sdl2.nuget.targets" Condition="Exists('packages\sdl2.nuget.2.30.7\build\native\sdl2.nuget.targets')" />
<Import Project="packages\libzip-c.1.9.2.6\build\native\libzip-c.targets" Condition="Exists('packages\libzip-c.1.9.2.6\build\native\libzip-c.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('packages\sdl2.nuget.redist.2.30.3\build\native\sdl2.nuget.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\sdl2.nuget.redist.2.30.3\build\native\sdl2.nuget.redist.targets'))" />
<Error Condition="!Exists('packages\sdl2.nuget.2.30.3\build\native\sdl2.nuget.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\sdl2.nuget.2.30.3\build\native\sdl2.nuget.targets'))" />
<Error Condition="!Exists('packages\sdl2.nuget.redist.2.30.7\build\native\sdl2.nuget.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\sdl2.nuget.redist.2.30.7\build\native\sdl2.nuget.redist.targets'))" />
<Error Condition="!Exists('packages\sdl2.nuget.2.30.7\build\native\sdl2.nuget.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\sdl2.nuget.2.30.7\build\native\sdl2.nuget.targets'))" />
<Error Condition="!Exists('packages\libzip-c.1.9.2.6\build\native\libzip-c.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\libzip-c.1.9.2.6\build\native\libzip-c.targets'))" />
</Target>
</Project>

View File

@ -25,7 +25,7 @@ private:
private:
UI ui;
const std::string m_version = "v0.2";
const std::string m_version = "v0.3";
std::string m_glslVer;
SDL_GLContext m_glContext;
SDL_Window* m_window;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="libzip-c" version="1.9.2.6" targetFramework="native" />
<package id="sdl2.nuget" version="2.30.3" targetFramework="native" />
<package id="sdl2.nuget.redist" version="2.30.3" targetFramework="native" />
<package id="sdl2.nuget" version="2.30.7" targetFramework="native" />
<package id="sdl2.nuget.redist" version="2.30.7" targetFramework="native" />
</packages>

View File

@ -5,6 +5,8 @@
#include <httplib.h>
#include <fstream>
#define FILEHOST_DOMAIN "www.online-ctr.com"
bool Requests::DownloadFile(const std::string& domain, const std::string& sitePath, const std::string& filePath)
{
httplib::SSLClient request(domain);
@ -35,7 +37,7 @@ bool Requests::DownloadFile(const std::string& domain, const std::string& sitePa
bool Requests::CheckUpdates(std::string& version)
{
httplib::SSLClient request("www.online-ctr.com");
httplib::SSLClient request(FILEHOST_DOMAIN);
httplib::Result response = request.Get("/wp-content/uploads/onlinectr_patches/build.txt");
if (response && response->status == 200) {
version = response->body;
@ -46,7 +48,7 @@ bool Requests::CheckUpdates(std::string& version)
bool Requests::DownloadUpdates(const std::string& path, std::string& status)
{
const std::string octrDomain = "www.online-ctr.com";
const std::string octrDomain = FILEHOST_DOMAIN;
const std::string octrPath = "/wp-content/uploads/onlinectr_patches/";
const std::vector<std::string> files = { g_clientString, g_patchString, g_configString };
if (!std::filesystem::is_directory(path)) { std::filesystem::create_directory(path); }

View File

@ -6,6 +6,9 @@
#include <portable-file-dialogs.h>
#include <filesystem>
#include <cstdlib>
#include <chrono>
#include <thread>
#include <iostream>
UI::UI()
{
@ -58,11 +61,18 @@ void UI::Render(int width, int height)
else if (!std::filesystem::exists(s_patchedPath)) { m_status = "Error: could not find " + s_patchedPath; }
else
{
m_status = "Launching game...";
g_dataManager.SaveData();
const std::string clientCommand = "start /b \"\" \"" + std::filesystem::current_path().string() + "/" + GetClientPath(m_version) + "\" " + m_username + " &";
std::system(clientCommand.c_str());
const std::string duckCommand = "start /b \"\" \"" + g_duckExecutable + "\" \"" + s_patchedPath + "\" &";
std::system(duckCommand.c_str());
{
using namespace std;
std::this_thread::sleep_for(7000ms); //it takes about this long for it to boot to the main menu.
}
//if you try to start the client immediately, it may attempt to access shmem immediately (and fail)
const std::string clientCommand = "start /b \"\" \"" + std::filesystem::current_path().string() + "/" + GetClientPath(m_version) + "\" " + m_username + " &";
std::system(clientCommand.c_str());
m_status = "Game launched...";
}
}
ImGui::EndDisabled();

View File

@ -46,7 +46,7 @@ bool Updater::CheckForUpdates(std::string& status, const std::string& currVersio
);
}
bool Updater::Update(std::string& status, std::string& currVersion, const std::string& gamePath, const std::string& biosPath)
bool Updater::Update(std::string& status, std::string& currVersion, const std::string& gamePath, const std::string& biosPath) //this function is literally called by the update BUTTON. Ensure that the user can update (i.e., replace their old version of duckstation via some method in the launcher) (e.g., purge duckstation, then update).
{
return StartRoutine([&]
{
@ -69,6 +69,7 @@ bool Updater::Update(std::string& status, std::string& currVersion, const std::s
return false;
}
status = "Installing custom settings...";
//setup bios
const std::string g_biosFolder = g_duckFolder + "bios/";
std::filesystem::create_directory(g_biosFolder);
std::string biosName;
@ -80,7 +81,8 @@ bool Updater::Update(std::string& status, std::string& currVersion, const std::s
break;
}
}
std::filesystem::copy_file(biosPath, g_biosFolder + biosName);
std::filesystem::copy_file(biosPath, g_biosFolder + biosName, std::filesystem::copy_options::overwrite_existing);
//setup portable.txt
const std::string duckPortable = g_duckFolder + "portable.txt";
std::ofstream portableFile(duckPortable.c_str());
portableFile.close();
@ -96,7 +98,13 @@ bool Updater::Update(std::string& status, std::string& currVersion, const std::s
std::string path = g_dataFolder + m_versionAvailable + "/";
if (Requests::DownloadUpdates(path, status) && Patch::NewVersion(path, gamePath, status))
{
if (copyIni) { std::filesystem::copy_file(GetIniPath_Version(m_versionAvailable), GetIniPath_Duck()); }
if (copyIni)
{
const std::string iniDir = g_duckFolder + "gamesettings/";
std::filesystem::create_directory(iniDir);
const std::string iniFilename = iniDir + g_configString;
std::filesystem::copy_file(GetIniPath_Version(m_versionAvailable), iniFilename, std::filesystem::copy_options::overwrite_existing);
}
m_updated = true;
m_updateAvailable = false;
currVersion = m_versionAvailable;