Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
033227eb83 | ||
|
|
388dd99f60 | ||
|
|
986db8acb8 | ||
|
|
1b94cbdd8d | ||
|
|
b2e2106912 | ||
|
|
3858d482e9 | ||
|
|
5e42a54bbf | ||
|
|
7766d47bda | ||
|
|
31dbcfdb4f | ||
|
|
43e2b69d66 | ||
|
|
7a0e0d1285 | ||
|
|
36d797b391 | ||
|
|
4e0b6440d9 | ||
|
|
eec90dae91 | ||
|
|
4e1c256fbe |
@@ -11,10 +11,9 @@ Also known as "The Cycle: Frontier".
|
||||
- [2. Unpack `Prospect.Server.Api`](#2-unpack-prospectserverapi)
|
||||
- [3. Patch the `hosts` file](#3-patch-the-hosts-file)
|
||||
- [4. Generate and import SSL certificate](#4-generate-and-import-ssl-certificate)
|
||||
- [5. Add `steam_appid` to the game](#5-add-steam_appid-to-the-game)
|
||||
- [6. Copy the `Prospect.Client.Loader` to the game](#6-copy-the-prospectclientloader-to-the-game)
|
||||
- [7. Run the server](#7-run-the-server)
|
||||
- [8. Run the game](#8-run-the-game)
|
||||
- [5. Extract `LoaderPack` to the game](#5-extract-loaderpack-to-the-game)
|
||||
- [6. Run the server](#6-run-the-server)
|
||||
- [7. Run the game](#7-run-the-game)
|
||||
- [Troubleshooting and FAQ](#troubleshooting-and-faq)
|
||||
- [How to remove the certificate?](#how-to-remove-the-certificate)
|
||||
- [`generate_ssl.exe` is flagged as a virus](#generate_sslexe-is-flagged-as-a-virus)
|
||||
@@ -30,10 +29,7 @@ Also known as "The Cycle: Frontier".
|
||||
* [x] EULA acceptance
|
||||
* [x] Tutorial
|
||||
* [x] Single-player station (Season 2 and Season 3):
|
||||
* [ ] Onboarding
|
||||
* [x] Talk to Badum
|
||||
* [x] Equip items
|
||||
* [ ] Deploy to Fortuna
|
||||
* [x] Onboarding
|
||||
* [ ] Matchmaking and deployment
|
||||
* [x] Solo
|
||||
* [ ] Squad
|
||||
@@ -42,16 +38,15 @@ Also known as "The Cycle: Frontier".
|
||||
* [x] Inventory and loadout
|
||||
* [ ] Loadout presets (Season 3)
|
||||
* [x] Quests
|
||||
* [ ] Job boards
|
||||
* [x] Faction progression
|
||||
* [ ] Season pass
|
||||
* [ ] Shops
|
||||
* [ ] Aurum Shops
|
||||
* [ ] Daily shop
|
||||
* [ ] Weekly shop
|
||||
* [ ] Shop rotation
|
||||
* [x] Daily login
|
||||
* [x] Character appearance and emotes
|
||||
* [x] Shops
|
||||
* [x] Item Shops
|
||||
* [x] Crafting station
|
||||
* [x] Quarters
|
||||
* [x] Player balance
|
||||
@@ -63,15 +58,16 @@ Also known as "The Cycle: Frontier".
|
||||
* [ ] Game mechanics
|
||||
* [x] Can deploy through terminal
|
||||
* [x] Can deploy with loadout
|
||||
* [ ] Can evac
|
||||
* [x] Can evac
|
||||
* [ ] Can do quests
|
||||
* [ ] Can gain/lose loot
|
||||
* [ ] Can use Alien Forge.
|
||||
* [ ] Map content
|
||||
* [x] Delivery quests
|
||||
* [ ] Kill quests
|
||||
* [x] Can gain/lose loot
|
||||
* [x] Can use Alien Forge.
|
||||
* [x] Map content
|
||||
* [x] Bright Sands
|
||||
* [x] Crescent Falls
|
||||
* [x] Tharis Island
|
||||
* [ ] Alien Forge
|
||||
|
||||
## Running locally
|
||||
|
||||
@@ -91,21 +87,17 @@ Before you start, you'll need the following software downloaded and installed:
|
||||
|
||||
1. [MongoDB Community Edition](https://fastdl.mongodb.org/windows/mongodb-windows-x86_64-8.0.4-signed.msi).
|
||||
|
||||
1. [Latest `Prospect.Server.Api`](https://github.com/deiteris/Prospect/releases) from the Releases section.
|
||||
1. [`Prospect.Server.Api` and `LoaderPack`](https://github.com/deiteris/Prospect/releases) from the Releases section:
|
||||
|
||||
1. [Latest `Prospect.Client.Loader`](https://github.com/deiteris/Prospect/releases) from the Releases section.
|
||||
- For Season 3 (the latest Steam game client), use Build 6.
|
||||
|
||||
1. [.NET Runtime 8.0](https://aka.ms/dotnet-core-applaunch?missing_runtime=true&arch=x64&rid=win-x64&os=win10&apphost_version=8.0.11) installed.
|
||||
|
||||
1. [ASP.NET Core 8.0](https://aka.ms/dotnet-core-applaunch?framework=Microsoft.AspNetCore.App&framework_version=8.0.0&arch=x64&rid=win-x64&os=win10) installed.
|
||||
- For Season 2 game client, use the latest version.
|
||||
|
||||
1. The Cycle: Frontier game client:
|
||||
|
||||
- The latest version from [Steam](https://steamcommunity.com/app/868270).
|
||||
|
||||
- Season 2 client. See [download instructions below](#11-how-to-download-season-2-client-from-steamdb-using-steam-console).
|
||||
|
||||
- Any other version, even Season 1 works.
|
||||
- Season 2 client version `4623363103423775682` from SteamDB. See [download instructions below](#11-how-to-download-season-2-client-from-steamdb-using-steam-console).
|
||||
|
||||
#### 1.1 How to download Season 2 client from SteamDB using Steam console
|
||||
|
||||
@@ -164,40 +156,24 @@ to successfully communicate with the local server. Do the following:
|
||||
|
||||
1. Click **Finish**. A **Security Warning** popup may appear, make sure it specifies `2EA46.playfabapi.com` certification authority and click **Yes**.
|
||||
|
||||
### 5. Add `steam_appid` to the game
|
||||
### 5. Extract `LoaderPack` to the game
|
||||
|
||||
1. Open the folder with The Cycle: Frontier and navigate to **Prospect** > **Binaries** > **Win64**.
|
||||
|
||||
1. Right-click in the folder > **New** > **Text Document**, and name it `steam_appid`.
|
||||
1. Open the `LoaderPack` archive.
|
||||
|
||||
1. Open `steam_appid` and enter `480`.
|
||||
|
||||
1. Save and close the file.
|
||||
|
||||
### 6. Copy the `Prospect.Client.Loader` to the game
|
||||
|
||||
> [!NOTE]
|
||||
> The current version of the loader will fix loot and mob spawning only for the following game client versions:
|
||||
>
|
||||
> - SteamDB Manifest `4623363103423775682`.
|
||||
> - The latest Steam client version.
|
||||
|
||||
1. Open the folder with The Cycle: Frontier and navigate to **Prospect** > **Binaries** > **Win64**.
|
||||
|
||||
1. Copy the downloaded `Prospect.Client.Loader` file into the folder.
|
||||
1. Drag and drop the contents of the `LoaderPack` archive to the game.
|
||||
|
||||
1. Create a shortcut for the `Prospect.Client.Loader` that you will use later to launch the game.
|
||||
|
||||
### 7. Run the server
|
||||
### 6. Run the server
|
||||
|
||||
Now you are all set! Open the folder with `Prospect.Server.Api` and run `Prospect.Server.Api.exe`. It will open a console if it runs successfully.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Do not close the console when you run the game.
|
||||
>
|
||||
> If the console does not open, make sure you have [.NET Runtime 8.0](https://aka.ms/dotnet-core-applaunch?missing_runtime=true&arch=x64&rid=win-x64&os=win10&apphost_version=8.0.11) and [ASP.NET Core 8.0](https://aka.ms/dotnet-core-applaunch?framework=Microsoft.AspNetCore.App&framework_version=8.0.0&arch=x64&rid=win-x64&os=win10) installed.
|
||||
|
||||
### 8. Run the game
|
||||
### 7. Run the game
|
||||
|
||||
Once the server is running, make sure that Steam is running and open The Cycle: Frontier using the shortcut you've created before.
|
||||
|
||||
|
||||
@@ -21,10 +21,9 @@
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{8170a17f-e536-4fdb-bc01-78bf09a9a48b}</ProjectGuid>
|
||||
<ProjectGuid>{04b1d963-2bcb-4887-9d6d-04cb3366a348}</ProjectGuid>
|
||||
<RootNamespace>ProspectClientLoader</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>Prospect.Client.Loader</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
|
||||
@@ -54,28 +54,354 @@ HANDLE OpenTargetProcess(DWORD processID) {
|
||||
return hProcess;
|
||||
}
|
||||
|
||||
bool CheckTutorialActorsOffset(HANDLE hProcess, uintptr_t address) {
|
||||
bool WriteBytes(HANDLE hProcess, uintptr_t address, BYTE* newValues, size_t size) {
|
||||
SIZE_T bytesWritten;
|
||||
DWORD oldProtect;
|
||||
if (VirtualProtectEx(hProcess, (LPVOID)address, size, PAGE_EXECUTE_READWRITE, &oldProtect)) {
|
||||
if (WriteProcessMemory(hProcess, (LPVOID)address, newValues, size, &bytesWritten)) {
|
||||
printf("Memory Write Success at address 0x%p\n", (void*)address);
|
||||
}
|
||||
else {
|
||||
printf("Failed to write memory. Error: %lu\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
// Restore original protection
|
||||
VirtualProtectEx(hProcess, (LPVOID)address, size, oldProtect, &oldProtect);
|
||||
}
|
||||
else {
|
||||
printf("Failed to change memory protection. Error: %lu\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckForceLevelStreamingOffset(HANDLE hProcess, uintptr_t address) {
|
||||
BYTE buffer[2];
|
||||
SIZE_T bytesRead;
|
||||
if (ReadProcessMemory(hProcess, (LPCVOID)address, buffer, sizeof(buffer), &bytesRead)) {
|
||||
return buffer[0] == 0x32 && buffer[1] == 0xDB;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
DWORD error = GetLastError();
|
||||
if (error == 299) {
|
||||
printf("Incomplete memory read. Retrying...\n");
|
||||
Sleep(1000);
|
||||
return CheckTutorialActorsOffset(hProcess, address);
|
||||
return CheckForceLevelStreamingOffset(hProcess, address);
|
||||
}
|
||||
printf("Failed to read memory. Error: %lu\n", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckGPStringOffset(HANDLE hProcess, uintptr_t address) {
|
||||
BYTE buffer[2];
|
||||
SIZE_T bytesRead;
|
||||
if (ReadProcessMemory(hProcess, (LPCVOID)address, buffer, sizeof(buffer), &bytesRead)) {
|
||||
return buffer[0] == 0x5F && buffer[1] == 0x00;
|
||||
}
|
||||
else {
|
||||
DWORD error = GetLastError();
|
||||
if (error == 299) {
|
||||
printf("Incomplete memory read. Retrying...\n");
|
||||
Sleep(1000);
|
||||
return CheckForceLevelStreamingOffset(hProcess, address);
|
||||
}
|
||||
printf("Failed to read memory. Error: %lu\n", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckWeakPointerCheckJumpOffset(HANDLE hProcess, uintptr_t address) {
|
||||
BYTE buffer[2];
|
||||
SIZE_T bytesRead;
|
||||
if (ReadProcessMemory(hProcess, (LPCVOID)address, buffer, sizeof(buffer), &bytesRead)) {
|
||||
return buffer[0] == 0xF8 && buffer[1] == 0xFE;
|
||||
}
|
||||
else {
|
||||
DWORD error = GetLastError();
|
||||
if (error == 299) {
|
||||
printf("Incomplete memory read. Retrying...\n");
|
||||
Sleep(1000);
|
||||
return CheckWeakPointerCheckJumpOffset(hProcess, address);
|
||||
}
|
||||
printf("Failed to read memory. Error: %lu\n", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckWeakPointerCheckOffset(HANDLE hProcess, uintptr_t address) {
|
||||
BYTE buffer[3];
|
||||
SIZE_T bytesRead;
|
||||
if (ReadProcessMemory(hProcess, (LPCVOID)address, buffer, sizeof(buffer), &bytesRead)) {
|
||||
return buffer[0] == 0x0F && buffer[1] == 0x1F && buffer[2] == 0x40;
|
||||
}
|
||||
else {
|
||||
DWORD error = GetLastError();
|
||||
if (error == 299) {
|
||||
printf("Incomplete memory read. Retrying...\n");
|
||||
Sleep(1000);
|
||||
return CheckWeakPointerCheckOffset(hProcess, address);
|
||||
}
|
||||
printf("Failed to read memory. Error: %lu\n", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PatchForceLevelStreaming(HANDLE hProcess, uintptr_t imageBase) {
|
||||
// Season 3 Patch force disable level streaming: sub_3DA7520 -> loc_3DA75B6
|
||||
uintptr_t address = imageBase + 0x3DA75B6;
|
||||
bool found = CheckForceLevelStreamingOffset(hProcess, address);
|
||||
if (!found) {
|
||||
// Season 2 Patch force disable level streaming: sub_3D19900 -> loc_3D19996
|
||||
address = imageBase + 0x3D19996;
|
||||
found = CheckForceLevelStreamingOffset(hProcess, address);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
printf("Failed to patch force level streaming!");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
BYTE newValues[2] = { 0xB3, 0x01 };
|
||||
bool result = WriteBytes(hProcess, address, newValues, sizeof(newValues));
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
address = imageBase + 0x5541C08;
|
||||
found = CheckGPStringOffset(hProcess, address);
|
||||
if (!found) {
|
||||
address = imageBase + 0x54E0558;
|
||||
found = CheckGPStringOffset(hProcess, address);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
printf("Failed to find _GP string!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
BYTE newValues[6] = { 0x47, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
bool result = WriteBytes(hProcess, address, newValues, sizeof(newValues));
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
address = imageBase + 0x16D7C24;
|
||||
found = CheckWeakPointerCheckOffset(hProcess, address);
|
||||
// TODO
|
||||
//if (!found) {
|
||||
// address = imageBase + 0x54E0558;
|
||||
// found = CheckWeakPointerCheckOffset(hProcess, address);
|
||||
//}
|
||||
|
||||
if (!found) {
|
||||
printf("Failed to find Weak Pointer Check!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
BYTE newValues[28] = {
|
||||
0x48, 0x8B, 0x06,
|
||||
0x48, 0x8D, 0x4D, 0x7F,
|
||||
0x48, 0x89, 0x45, 0x7F,
|
||||
0xE8, 0xDC, 0xC7, 0xA7, 0x00,
|
||||
0x48, 0x85, 0xC0,
|
||||
0x0F, 0x84, 0xEE, 0x00, 0x00, 0x00,
|
||||
0x90, 0x90, 0x90
|
||||
};
|
||||
bool result = WriteBytes(hProcess, address, newValues, sizeof(newValues));
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
address = imageBase + 0x16D7D34;
|
||||
found = CheckWeakPointerCheckJumpOffset(hProcess, address);
|
||||
// TODO
|
||||
//if (!found) {
|
||||
// address = imageBase + 0x54E0558;
|
||||
// found = CheckWeakPointerCheckJumpOffset(hProcess, address);
|
||||
//}
|
||||
|
||||
if (!found) {
|
||||
printf("Failed to find Weak Pointer Check Jump!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
BYTE newValues[1] = { 0xEC };
|
||||
bool result = WriteBytes(hProcess, address, newValues, sizeof(newValues));
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckUpdateInventoryFunctionOffset(HANDLE hProcess, uintptr_t address) {
|
||||
BYTE buffer[2];
|
||||
SIZE_T bytesRead;
|
||||
if (ReadProcessMemory(hProcess, (LPCVOID)address, buffer, sizeof(buffer), &bytesRead)) {
|
||||
return buffer[0] == 0x84 && buffer[1] == 0xC0;
|
||||
}
|
||||
else {
|
||||
DWORD error = GetLastError();
|
||||
if (error == 299) {
|
||||
printf("Incomplete memory read. Retrying...\n");
|
||||
Sleep(1000);
|
||||
return CheckUpdateInventoryFunctionOffset(hProcess, address);
|
||||
}
|
||||
printf("Failed to read memory. Error: %lu\n", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PatchUpdateInventory(HANDLE hProcess, uintptr_t imageBase) {
|
||||
uintptr_t address = imageBase + 0x18D5A11;
|
||||
bool found = CheckUpdateInventoryFunctionOffset(hProcess, address);
|
||||
if (!found) {
|
||||
address = imageBase + 0x3D19B19;
|
||||
found = CheckUpdateInventoryFunctionOffset(hProcess, address);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
printf("Failed to patch inventory update function!");
|
||||
return false;
|
||||
}
|
||||
|
||||
BYTE newValues[8] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
|
||||
bool result = WriteBytes(hProcess, address, newValues, sizeof(newValues));
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckSetupPlayerContractsOffset(HANDLE hProcess, uintptr_t address) {
|
||||
BYTE buffer[2];
|
||||
SIZE_T bytesRead;
|
||||
if (ReadProcessMemory(hProcess, (LPCVOID)address, buffer, sizeof(buffer), &bytesRead)) {
|
||||
return buffer[0] == 0x48 && buffer[1] == 0x8D;
|
||||
}
|
||||
else {
|
||||
DWORD error = GetLastError();
|
||||
if (error == 299) {
|
||||
printf("Incomplete memory read. Retrying...\n");
|
||||
Sleep(1000);
|
||||
return CheckSetupPlayerContractsOffset(hProcess, address);
|
||||
}
|
||||
printf("Failed to read memory. Error: %lu\n", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckUpdatePlayerContractsOffset(HANDLE hProcess, uintptr_t address) {
|
||||
BYTE buffer[2];
|
||||
SIZE_T bytesRead;
|
||||
if (ReadProcessMemory(hProcess, (LPCVOID)address, buffer, sizeof(buffer), &bytesRead)) {
|
||||
return buffer[0] == 0x3B && buffer[1] == 0xC1;
|
||||
}
|
||||
else {
|
||||
DWORD error = GetLastError();
|
||||
if (error == 299) {
|
||||
printf("Incomplete memory read. Retrying...\n");
|
||||
Sleep(1000);
|
||||
return CheckUpdatePlayerContractsOffset(hProcess, address);
|
||||
}
|
||||
printf("Failed to read memory. Error: %lu\n", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PatchLoadPlayerContracts(HANDLE hProcess, uintptr_t imageBase) {
|
||||
uintptr_t address = imageBase + 0x171E9EC;
|
||||
bool found = CheckUpdatePlayerContractsOffset(hProcess, address);
|
||||
if (!found) {
|
||||
printf("Failed to update player contracts function!");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
BYTE newValues[4] = { 0x90, 0x90, 0xEB, 0x21 };
|
||||
bool result = WriteBytes(hProcess, address, newValues, sizeof(newValues));
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
address = imageBase + 0x1706127;
|
||||
found = CheckSetupPlayerContractsOffset(hProcess, address);
|
||||
if (!found) {
|
||||
printf("Failed to find setup player contracts function!");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
BYTE newValues[39] = {
|
||||
0x45, 0x33, 0xC0, 0x49, 0x8B, 0xD5, 0x48, 0x8B, 0x89, 0xD8, 0x00, 0x00, 0x00, 0xE8, 0x47,
|
||||
0xE2, 0x10, 0x00, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
|
||||
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xEB
|
||||
};
|
||||
bool result = WriteBytes(hProcess, address, newValues, sizeof(newValues));
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Inject(HANDLE hProcess) {
|
||||
const wchar_t* dllPath = L"UE4SS.dll";
|
||||
// Allocate space in the target process for the DLL path
|
||||
size_t pathSize = (wcslen(dllPath) + 1) * sizeof(wchar_t);
|
||||
LPVOID remotePath = VirtualAllocEx(hProcess, NULL, pathSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!remotePath) {
|
||||
wprintf(L"Failed to allocate memory in remote process. Error: %lu\n", GetLastError());
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write the DLL path into the target process
|
||||
if (!WriteProcessMemory(hProcess, remotePath, dllPath, pathSize, NULL)) {
|
||||
wprintf(L"Failed to write memory. Error: %lu\n", GetLastError());
|
||||
VirtualFreeEx(hProcess, remotePath, 0, MEM_RELEASE);
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the address of LoadLibraryW in kernel32.dll
|
||||
LPVOID loadLibraryAddr = (LPVOID)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW");
|
||||
if (!loadLibraryAddr) {
|
||||
wprintf(L"Failed to get LoadLibraryW address. Error: %lu\n", GetLastError());
|
||||
VirtualFreeEx(hProcess, remotePath, 0, MEM_RELEASE);
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a remote thread to call LoadLibraryW with our DLL path
|
||||
HANDLE hThread = CreateRemoteThread(
|
||||
hProcess, NULL, 0,
|
||||
(LPTHREAD_START_ROUTINE)loadLibraryAddr,
|
||||
remotePath, 0, NULL);
|
||||
|
||||
if (!hThread) {
|
||||
wprintf(L"Failed to create remote thread. Error: %lu\n", GetLastError());
|
||||
VirtualFreeEx(hProcess, remotePath, 0, MEM_RELEASE);
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Patch(const wchar_t* processName) {
|
||||
DWORD processID;
|
||||
uintptr_t imageBase, address;
|
||||
BYTE newValues[2] = { 0xB3, 0x01 };
|
||||
SIZE_T bytesWritten;
|
||||
uintptr_t imageBase;
|
||||
|
||||
HANDLE hProcess = StartProcess(processName, &processID);
|
||||
if (hProcess == NULL) {
|
||||
@@ -92,33 +418,13 @@ bool Patch(const wchar_t* processName) {
|
||||
}
|
||||
printf("PE Image Base Address: 0x%p\n", (void*)imageBase);
|
||||
|
||||
// Season 3 Patch tutorial actors loader: sub_3DA7520 -> loc_3DA75B6
|
||||
address = imageBase + 0x3DA75B6;
|
||||
bool found = CheckTutorialActorsOffset(hProcess, address);
|
||||
if (!found) {
|
||||
// Season 2 Patch tutorial actors loader: sub_3D19900 -> loc_3D19996
|
||||
address = imageBase + 0x3D19996;
|
||||
found = CheckTutorialActorsOffset(hProcess, address);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
printf("Failed to find necessary memory chunk!");
|
||||
bool success = !PatchForceLevelStreaming(hProcess, imageBase) || !PatchUpdateInventory(hProcess, imageBase) || !PatchLoadPlayerContracts(hProcess, imageBase);
|
||||
if (success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD oldProtect;
|
||||
if (VirtualProtectEx(hProcess, (LPVOID)address, sizeof(newValues), PAGE_EXECUTE_READWRITE, &oldProtect)) {
|
||||
if (WriteProcessMemory(hProcess, (LPVOID)address, newValues, sizeof(newValues), &bytesWritten)) {
|
||||
printf("Memory Write Success: Wrote 0xB3 0x01 at address 0x%p\n", (void*)address);
|
||||
}
|
||||
else {
|
||||
printf("Failed to write memory. Error: %lu\n", GetLastError());
|
||||
}
|
||||
// Restore original protection
|
||||
VirtualProtectEx(hProcess, (LPVOID)address, sizeof(newValues), oldProtect, &oldProtect);
|
||||
}
|
||||
else {
|
||||
printf("Failed to change memory protection. Error: %lu\n", GetLastError());
|
||||
if (!Inject(hProcess)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
@@ -126,8 +432,42 @@ bool Patch(const wchar_t* processName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteSteamAppIDFile() {
|
||||
const char* filename = "steam_appid.txt";
|
||||
FILE* fp;
|
||||
|
||||
if (fopen_s(&fp, filename, "r") == 0) {
|
||||
// File exists, close the file
|
||||
fclose(fp);
|
||||
printf("File '%s' already exists.\n", filename);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// File does not exist, create and write to it
|
||||
if (fopen_s(&fp, filename, "w") == 0) {
|
||||
const char* content = "480";
|
||||
size_t written = fwrite(content, sizeof(char), strlen(content), fp);
|
||||
fclose(fp);
|
||||
printf("File '%s' was created and written successfully.\n", filename);
|
||||
}
|
||||
else {
|
||||
printf("Error creating the file '%s'.\n", filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main() {
|
||||
bool success = Patch(L"Prospect-Win64-Shipping.exe");
|
||||
bool success = WriteSteamAppIDFile();
|
||||
if (!success) {
|
||||
// Wait for user input to exit
|
||||
printf("An error occurred when writing steam_appid.txt! Press any key to exit...\n");
|
||||
getchar();
|
||||
return 1;
|
||||
}
|
||||
|
||||
success = Patch(L"Prospect-Win64-Shipping.exe");
|
||||
if (!success) {
|
||||
// Wait for user input to exit
|
||||
printf("An error occurred when patching the executable! Press any key to exit...\n");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Prospect.Server.Api.Services.CloudScript.Models.Data;
|
||||
|
||||
namespace Prospect.Server.Api.Models.Data;
|
||||
|
||||
@@ -11,5 +12,14 @@ public class PlayerBalance
|
||||
public int SoftCurrency { get; set; }
|
||||
|
||||
[JsonPropertyName("IN")]
|
||||
public int InsuranceTokens { get; set; }
|
||||
public int InsuranceCurrency { get; set; }
|
||||
}
|
||||
|
||||
public class JobBoardsData
|
||||
{
|
||||
[JsonPropertyName("lastBoardRefreshTimeUtc")]
|
||||
public FYTimestamp LastBoardRefreshTimeUtc { get; set; }
|
||||
|
||||
[JsonPropertyName("boards")]
|
||||
public List<FYFactionContractsData> Boards { get; set; }
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Prospect.Server.Api.Services.Auth.Extensions;
|
||||
using Prospect.Server.Api.Services.CloudScript.Models.Data;
|
||||
@@ -59,8 +59,7 @@ public class ActivateContract : ICloudScriptFunction<FYActivateContractRequest,
|
||||
var titleData = _titleDataService.Find(new List<string>{"Contracts"});
|
||||
|
||||
var contracts = JsonSerializer.Deserialize<Dictionary<string, TitleDataContractInfo>>(titleData["Contracts"]);
|
||||
var contract = contracts[request.ContractID];
|
||||
if (contract == null) {
|
||||
if (!contracts.TryGetValue(request.ContractID, out var contract)) {
|
||||
return new FYActivateContractResult
|
||||
{
|
||||
UserID = userId,
|
||||
@@ -71,6 +70,14 @@ public class ActivateContract : ICloudScriptFunction<FYActivateContractRequest,
|
||||
// TODO: Contract unlock criteria
|
||||
|
||||
var progress = new int[contract.Objectives.Length];
|
||||
// TODO: Automatically set max progress of kill and visit objectives. To remove once kill and visit objectives are reported by the client.
|
||||
for (var i = 0; i < contract.Objectives.Length; i++) {
|
||||
var objective = contract.Objectives[i];
|
||||
if (objective.Type != EYContractObjectiveType.OwnNumOfItem) {
|
||||
progress[i] = objective.MaxProgress;
|
||||
}
|
||||
}
|
||||
|
||||
var contractsActive = JsonSerializer.Deserialize<FYGetActiveContractsResult>(userData["ContractsActive"].Value);
|
||||
var contractData = new FYActiveContractPlayerData {
|
||||
ContractID = request.ContractID,
|
||||
|
||||
@@ -50,6 +50,8 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly UserDataService _userDataService;
|
||||
private readonly TitleDataService _titleDataService;
|
||||
private readonly string[] difficulties = ["Easy", "Medium", "Hard"];
|
||||
private readonly Random rnd = new Random();
|
||||
|
||||
public ClaimActiveContract(IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService)
|
||||
{
|
||||
@@ -66,11 +68,10 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
|
||||
throw new CloudScriptException("CloudScript was not called within a http request");
|
||||
}
|
||||
var userId = context.User.FindAuthUserId();
|
||||
var titleData = _titleDataService.Find(new List<string>{"Contracts", "Blueprints"});
|
||||
var titleData = _titleDataService.Find(new List<string>{ "Contracts", "Blueprints", "Jobs", "LevelData" });
|
||||
|
||||
var contracts = JsonSerializer.Deserialize<Dictionary<string, TitleDataContractInfo>>(titleData["Contracts"]);
|
||||
var contract = contracts[request.ContractID];
|
||||
if (contract == null) {
|
||||
if (!contracts.TryGetValue(request.ContractID, out var contract)) {
|
||||
return new FYClaimCompletedActiveContractRewardsResult
|
||||
{
|
||||
UserID = userId,
|
||||
@@ -82,16 +83,17 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
|
||||
var factionKey = "FactionProgression" + contract.Faction;
|
||||
var userData = await _userDataService.FindAsync(
|
||||
userId, userId,
|
||||
new List<string>{"ContractsActive", "ContractsOneTimeCompleted", "Balance", "Inventory", factionKey}
|
||||
new List<string>{"ContractsActive", "ContractsOneTimeCompleted", "Balance", "Inventory", factionKey, "JobBoardsData" }
|
||||
);
|
||||
|
||||
var factionProgression = JsonSerializer.Deserialize<int>(userData[factionKey].Value);
|
||||
var contractsActive = JsonSerializer.Deserialize<FYGetActiveContractsResult>(userData["ContractsActive"].Value);
|
||||
var contractsCompleted = JsonSerializer.Deserialize<FYGetCompletedContractsResult>(userData["ContractsOneTimeCompleted"].Value);
|
||||
var jobBoardsData = JsonSerializer.Deserialize<JobBoardsData>(userData["JobBoardsData"].Value);
|
||||
var balance = JsonSerializer.Deserialize<PlayerBalance>(userData["Balance"].Value);
|
||||
var inventory = JsonSerializer.Deserialize<List<FYCustomItemInfo>>(userData["Inventory"].Value);
|
||||
|
||||
var targetContractIdx = contractsActive.Contracts.FindIndex(item => item.ContractID == request.ContractID);
|
||||
var targetContractIdx = contractsActive.Contracts.FindIndex(item => item.ContractID == contract.Name);
|
||||
if (targetContractIdx == -1) {
|
||||
return new FYClaimCompletedActiveContractRewardsResult
|
||||
{
|
||||
@@ -106,6 +108,7 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
|
||||
var targetContract = contractsActive.Contracts[targetContractIdx];
|
||||
List<FYCustomItemInfo> itemsUpdatedOrRemoved = [];
|
||||
HashSet<string> deletedItemsIds = [];
|
||||
// TODO: Optimize
|
||||
for (var i = 0; i < contract.Objectives.Length; i++) {
|
||||
var objective = contract.Objectives[i];
|
||||
int remaining = objective.MaxProgress;
|
||||
@@ -161,9 +164,9 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
|
||||
} else if (reward.ItemID == "Aurum") {
|
||||
balance.HardCurrency += reward.Amount;
|
||||
changedCurrencies.Add(new FYCurrencyItem { CurrencyName = "Aurum", Amount = balance.HardCurrency });
|
||||
} else if (reward.ItemID == "InsuranceToken") {
|
||||
balance.InsuranceTokens += reward.Amount;
|
||||
changedCurrencies.Add(new FYCurrencyItem { CurrencyName = "InsuranceToken", Amount = balance.InsuranceTokens });
|
||||
} else if (reward.ItemID == "InsuranceCurrency") {
|
||||
balance.InsuranceCurrency += reward.Amount;
|
||||
changedCurrencies.Add(new FYCurrencyItem { CurrencyName = "InsuranceCurrency", Amount = balance.InsuranceCurrency });
|
||||
} else {
|
||||
// TODO: Inventory limit check
|
||||
|
||||
@@ -211,14 +214,44 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
|
||||
factionProgression += contract.ReputationIncrease;
|
||||
contractsActive.Contracts.RemoveAt(targetContractIdx);
|
||||
// Main contracts can be completed only once.
|
||||
// Other contracts should be jobs.
|
||||
var newContractIdOnBoard = "";
|
||||
if (contract.IsMainContract) {
|
||||
contractsCompleted.ContractsIDs.Add(request.ContractID);
|
||||
contractsCompleted.ContractsIDs.Add(contract.Name);
|
||||
} else {
|
||||
var parts = contract.Name.Split('-');
|
||||
var difficulty = parts[1];
|
||||
|
||||
var levelsData = JsonSerializer.Deserialize<Dictionary<string, int[]>>(titleData["LevelData"]);
|
||||
var levels = levelsData[contract.Faction];
|
||||
var jobs = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string[]>>>(titleData["Jobs"]);
|
||||
var level = levels.Length - Array.FindIndex(levels, (int xp) => {
|
||||
return factionProgression >= xp;
|
||||
});
|
||||
|
||||
var factionBoard = jobBoardsData.Boards.Find(c => c.FactionId == contract.Faction);
|
||||
var idx = Array.FindIndex(difficulties, d => d == difficulty);
|
||||
var factionJobs = jobs[contract.Faction];
|
||||
var jobsAvailable = Array.FindAll(factionJobs[difficulty], (string jobId) => {
|
||||
return level >= contracts[jobId].UnlockData.Level;
|
||||
});
|
||||
// If player completes a job, it means he has at least one unlocked anyway.
|
||||
newContractIdOnBoard = jobsAvailable[rnd.Next(jobsAvailable.Length)];
|
||||
factionBoard.Contracts[idx].ContractId = newContractIdOnBoard;
|
||||
}
|
||||
|
||||
// TODO: Check for next unlock in contracts instead of relying on client
|
||||
var newContracts = new List<FYActiveContractPlayerData>();
|
||||
foreach (var contractIdToUnlock in request.ContractsToUnlock) {
|
||||
var contractToUnlock = contracts[contractIdToUnlock];
|
||||
var progress = new int[contractToUnlock.Objectives.Length];
|
||||
// TODO: Automatically set max progress of kill and visit objectives. To remove once kill and visit objectives are reported by the client.
|
||||
for (var i = 0; i < contractToUnlock.Objectives.Length; i++) {
|
||||
var objective = contractToUnlock.Objectives[i];
|
||||
if (objective.Type != EYContractObjectiveType.OwnNumOfItem) {
|
||||
progress[i] = objective.MaxProgress;
|
||||
}
|
||||
}
|
||||
var newContract = new FYActiveContractPlayerData {
|
||||
ContractID = contractIdToUnlock,
|
||||
Progress = progress,
|
||||
@@ -235,6 +268,7 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
|
||||
["Balance"] = JsonSerializer.Serialize(balance),
|
||||
["Inventory"] = JsonSerializer.Serialize(newInventory),
|
||||
[factionKey] = JsonSerializer.Serialize(factionProgression),
|
||||
["JobBoardsData"] = JsonSerializer.Serialize(jobBoardsData),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -242,12 +276,12 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
|
||||
{
|
||||
UserID = userId,
|
||||
Error = "",
|
||||
ClaimedContractID = request.ContractID,
|
||||
ClaimedContractID = contract.Name,
|
||||
ContractsActivated = newContracts,
|
||||
ItemsUpdatedOrRemoved = itemsUpdatedOrRemoved,
|
||||
ChangedCurrencies = changedCurrencies.ToArray(),
|
||||
ItemsGranted = itemsGranted,
|
||||
NewContractIdOnBoard = "", // TODO: This should be a replacement contract apparently?
|
||||
NewContractIdOnBoard = newContractIdOnBoard,
|
||||
PlayerFactionProgressData = new FYPlayerFactionProgressData {
|
||||
FactionID = contract.Faction,
|
||||
CurrentProgression = factionProgression,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Prospect.Server.Api.Hubs;
|
||||
using Prospect.Server.Api.Services.CloudScript.Models;
|
||||
|
||||
// Closed Beta Function
|
||||
|
||||
namespace Prospect.Server.Api.Services.CloudScript.Functions;
|
||||
|
||||
[CloudScriptFunction("EnterMatchmaking")]
|
||||
public class EnterMatchmakingFunction : ICloudScriptFunction<FYEnterMatchAzureFunction, FYEnterMatchAzureFunctionResult>
|
||||
{
|
||||
private readonly IHubContext<CycleHub> _hubContext;
|
||||
|
||||
public EnterMatchmakingFunction(IHubContext<CycleHub> hubContext)
|
||||
{
|
||||
_hubContext = hubContext;
|
||||
}
|
||||
|
||||
public async Task<FYEnterMatchAzureFunctionResult> ExecuteAsync(FYEnterMatchAzureFunction request)
|
||||
{
|
||||
//await _hubContext.Clients.All.SendAsync("OnSquadMatchmakingSuccess", new OnSquadMatchmakingSuccessMessage {
|
||||
// Success = true,
|
||||
// SessionID = request.MapName, // TODO: Need to implement TryGetCompleteSquadInfo and pass squad info
|
||||
// SquadID = request.SquadId,
|
||||
//});
|
||||
|
||||
return new FYEnterMatchAzureFunctionResult
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = "",
|
||||
SingleplayerStation = true,
|
||||
Address = request.MapName,
|
||||
MaintenanceMode = false,
|
||||
Port = 7777,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Prospect.Server.Api.Hubs;
|
||||
using Prospect.Server.Api.Services.Auth.Extensions;
|
||||
using Prospect.Server.Api.Services.CloudScript.Models;
|
||||
using Prospect.Server.Api.Services.UserData;
|
||||
|
||||
namespace Prospect.Server.Api.Services.CloudScript.Functions;
|
||||
|
||||
@@ -17,15 +20,72 @@ public class OnSquadMatchmakingSuccessMessage {
|
||||
[CloudScriptFunction("EnterMatchmakingMatch")]
|
||||
public class EnterMatchmakingMatchFunction : ICloudScriptFunction<FYEnterMatchAzureFunction, FYEnterMatchmakingResult>
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IHubContext<CycleHub> _hubContext;
|
||||
private readonly UserDataService _userDataService;
|
||||
private readonly TitleDataService _titleDataService;
|
||||
|
||||
public EnterMatchmakingMatchFunction(IHubContext<CycleHub> hubContext)
|
||||
public EnterMatchmakingMatchFunction(IHubContext<CycleHub> hubContext, IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_hubContext = hubContext;
|
||||
_userDataService = userDataService;
|
||||
_titleDataService = titleDataService;
|
||||
}
|
||||
|
||||
public async Task<FYEnterMatchmakingResult> ExecuteAsync(FYEnterMatchAzureFunction request)
|
||||
{
|
||||
var context = _httpContextAccessor.HttpContext;
|
||||
if (context == null)
|
||||
{
|
||||
throw new CloudScriptException("CloudScript was not called within a http request");
|
||||
}
|
||||
|
||||
var userId = context.User.FindAuthUserId();
|
||||
var titleData = _titleDataService.Find(new List<string>{"Contracts"});
|
||||
var contracts = JsonSerializer.Deserialize<Dictionary<string, TitleDataContractInfo>>(titleData["Contracts"]);
|
||||
|
||||
var userData = await _userDataService.FindAsync(
|
||||
userId, userId,
|
||||
new List<string>{"ContractsActive", "Inventory"}
|
||||
);
|
||||
var inventory = JsonSerializer.Deserialize<List<FYCustomItemInfo>>(userData["Inventory"].Value);
|
||||
var contractsActive = JsonSerializer.Deserialize<FYGetActiveContractsResult>(userData["ContractsActive"].Value);
|
||||
// Compute delivery quest progress before deploy.
|
||||
// This ensures that the quest progress will be shown on the planet.
|
||||
// The station doesn't seem to care about delivery quests progress and calculates progress
|
||||
// based on actual items in stash. And so does the "ClaimActiveContract" function.
|
||||
foreach (var contractActive in contractsActive.Contracts) {
|
||||
if (!contracts.TryGetValue(contractActive.ContractID, out var contract)) {
|
||||
continue;
|
||||
}
|
||||
for (var i = 0; i < contract.Objectives.Length; i++) {
|
||||
var objective = contract.Objectives[i];
|
||||
if (objective.Type != EYContractObjectiveType.OwnNumOfItem) {
|
||||
continue;
|
||||
}
|
||||
int remaining = objective.MaxProgress;
|
||||
foreach (var item in inventory) {
|
||||
if (item.BaseItemId != objective.ItemToOwn) {
|
||||
continue;
|
||||
}
|
||||
remaining -= item.Amount;
|
||||
if (remaining <= 0) {
|
||||
remaining = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
contractActive.Progress[i] = objective.MaxProgress - remaining;
|
||||
}
|
||||
}
|
||||
|
||||
await _userDataService.UpdateAsync(
|
||||
userId, userId,
|
||||
new Dictionary<string, string>{
|
||||
["ContractsActive"] = JsonSerializer.Serialize(contractsActive),
|
||||
}
|
||||
);
|
||||
|
||||
await _hubContext.Clients.All.SendAsync("OnSquadMatchmakingSuccess", new OnSquadMatchmakingSuccessMessage {
|
||||
Success = true,
|
||||
SessionID = request.MapName, // TODO: Need to implement TryGetCompleteSquadInfo and pass squad info
|
||||
|
||||
+71
-70
@@ -1,4 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using System.Collections;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Text.Json;
|
||||
using Prospect.Server.Api.Models.Data;
|
||||
using Prospect.Server.Api.Services.Auth.Extensions;
|
||||
using Prospect.Server.Api.Services.CloudScript.Models;
|
||||
using Prospect.Server.Api.Services.CloudScript.Models.Data;
|
||||
@@ -11,11 +14,16 @@ public class RequestActiveObjectivesAndBoardsData : ICloudScriptFunction<FYQuery
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly UserDataService _userDataService;
|
||||
private readonly TitleDataService _titleDataService;
|
||||
private readonly string[] factions = ["Korolev", "Osiris", "ICA"]; // TODO: Order is important
|
||||
private readonly string[] difficulties = ["Easy", "Medium", "Hard"];
|
||||
private readonly Random rnd = new Random();
|
||||
|
||||
public RequestActiveObjectivesAndBoardsData(IHttpContextAccessor httpContextAccessor, UserDataService userDataService)
|
||||
public RequestActiveObjectivesAndBoardsData(IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_userDataService = userDataService;
|
||||
_titleDataService = titleDataService;
|
||||
}
|
||||
|
||||
public async Task<FYGetPlayerContractsResult> ExecuteAsync(FYQueryFactionProgressionRequest request)
|
||||
@@ -26,10 +34,65 @@ public class RequestActiveObjectivesAndBoardsData : ICloudScriptFunction<FYQuery
|
||||
return new FYGetPlayerContractsResult{};
|
||||
}
|
||||
var userId = context.User.FindAuthUserId();
|
||||
var userData = await _userDataService.FindAsync(userId, userId, new List<string>{"ContractsActive"});
|
||||
var contractsActive = JsonSerializer.Deserialize<FYGetActiveContractsResult>(userData["ContractsActive"].Value);
|
||||
|
||||
// TODO: Get player's contract boards and refresh
|
||||
var userData = await _userDataService.FindAsync(userId, userId, new List<string>{ "ContractsActive", "JobBoardsData", "FactionProgressionKorolev", "FactionProgressionICA", "FactionProgressionOsiris" });
|
||||
var contractsActive = JsonSerializer.Deserialize<FYGetActiveContractsResult>(userData["ContractsActive"].Value);
|
||||
var jobBoardsData = JsonSerializer.Deserialize<JobBoardsData>(userData["JobBoardsData"].Value);
|
||||
|
||||
var now = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
var lastBoardRefreshTimeUtc = jobBoardsData.LastBoardRefreshTimeUtc;
|
||||
var boards = jobBoardsData.Boards;
|
||||
// The boards are updated once in 3 hours
|
||||
if (now >= lastBoardRefreshTimeUtc.Seconds + 60 * 60 * 3) {
|
||||
var titleData = _titleDataService.Find(new List<string> { "Contracts", "Jobs", "LevelData" });
|
||||
|
||||
var contracts = JsonSerializer.Deserialize<Dictionary<string, TitleDataContractInfo>>(titleData["Contracts"]);
|
||||
var jobs = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string[]>>>(titleData["Jobs"]);
|
||||
var levelsData = JsonSerializer.Deserialize<Dictionary<string, int[]>>(titleData["LevelData"]);
|
||||
var currentQuests = new Dictionary<string, Dictionary<string, bool>> {
|
||||
["Korolev"] = new Dictionary<string, bool> { ["Easy"] = false, ["Medium"] = false, ["Hard"] = false },
|
||||
["Osiris"] = new Dictionary<string, bool> { ["Easy"] = false, ["Medium"] = false, ["Hard"] = false },
|
||||
["ICA"] = new Dictionary<string, bool> { ["Easy"] = false, ["Medium"] = false, ["Hard"] = false },
|
||||
};
|
||||
|
||||
foreach (var contract in contractsActive.Contracts) {
|
||||
var parts = contract.ContractID.Split('-');
|
||||
if (parts[0] != "NEW") {
|
||||
continue;
|
||||
}
|
||||
var difficulty = parts[1];
|
||||
var faction = parts[2] == "KOR" ? "Korolev" : parts[2];
|
||||
currentQuests[faction][difficulty] = true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < factions.Length; i++) {
|
||||
var faction = factions[i];
|
||||
var factionProgression = JsonSerializer.Deserialize<int>(userData[$"FactionProgression{faction}"].Value);
|
||||
var levels = levelsData[faction];
|
||||
var level = levels.Length - Array.FindIndex(levels, (int xp) => {
|
||||
return factionProgression >= xp;
|
||||
});
|
||||
|
||||
for (var j = 0; j < difficulties.Length; j++) {
|
||||
var difficulty = difficulties[j];
|
||||
if (currentQuests[faction][difficulty]) {
|
||||
continue;
|
||||
}
|
||||
var factionJobs = jobs[faction];
|
||||
var jobsAvailable = Array.FindAll(factionJobs[difficulty], (string jobId) => {
|
||||
return level >= contracts[jobId].UnlockData.Level;
|
||||
});
|
||||
string jobId = jobsAvailable.Length > 0 ? jobsAvailable[rnd.Next(jobsAvailable.Length)] : factionJobs[difficulty][0];
|
||||
boards[i].Contracts[j] = new FYFactionContractData { ContractId = jobId };
|
||||
}
|
||||
}
|
||||
lastBoardRefreshTimeUtc.Seconds = now;
|
||||
|
||||
await _userDataService.UpdateAsync(
|
||||
userId, userId,
|
||||
new Dictionary<string, string> { ["JobBoardsData"] = JsonSerializer.Serialize(jobBoardsData) }
|
||||
);
|
||||
}
|
||||
|
||||
return new FYGetPlayerContractsResult
|
||||
{
|
||||
@@ -38,72 +101,10 @@ public class RequestActiveObjectivesAndBoardsData : ICloudScriptFunction<FYQuery
|
||||
ActiveContracts = contractsActive.Contracts,
|
||||
FactionsContracts = new FYFactionsContractsData
|
||||
{
|
||||
Boards = new List<FYFactionContractsData>
|
||||
{
|
||||
new FYFactionContractsData
|
||||
{
|
||||
FactionId = "ICA",
|
||||
Contracts = new List<FYFactionContractData>
|
||||
{
|
||||
new FYFactionContractData
|
||||
{
|
||||
ContractId = "NEW-Easy-ICA-Gather-1",
|
||||
Boards = boards,
|
||||
LastBoardRefreshTimeUtc = lastBoardRefreshTimeUtc
|
||||
},
|
||||
new FYFactionContractData
|
||||
{
|
||||
ContractId = "NEW-Medium-ICA-Uplink-1",
|
||||
},
|
||||
new FYFactionContractData
|
||||
{
|
||||
ContractId = "NEW-Hard-ICA-Uplink-1",
|
||||
}
|
||||
}
|
||||
},
|
||||
new FYFactionContractsData
|
||||
{
|
||||
FactionId = "Korolev",
|
||||
Contracts = new List<FYFactionContractData>
|
||||
{
|
||||
new FYFactionContractData
|
||||
{
|
||||
ContractId = "NEW-Easy-KOR-Mine-4",
|
||||
},
|
||||
new FYFactionContractData
|
||||
{
|
||||
ContractId = "NEW-Medium-KOR-Mine-1",
|
||||
},
|
||||
new FYFactionContractData
|
||||
{
|
||||
ContractId = "NEW-Hard-KOR-PvP-6",
|
||||
}
|
||||
}
|
||||
},
|
||||
new FYFactionContractsData
|
||||
{
|
||||
FactionId = "Osiris",
|
||||
Contracts = new List<FYFactionContractData>
|
||||
{
|
||||
new FYFactionContractData
|
||||
{
|
||||
ContractId = "NEW-Easy-Osiris-Brightcaps-1",
|
||||
},
|
||||
new FYFactionContractData
|
||||
{
|
||||
ContractId = "NEW-Medium-Osiris-Gather-1",
|
||||
},
|
||||
new FYFactionContractData
|
||||
{
|
||||
ContractId = "NEW-Hard-Osiris-Gather-7",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
LastBoardRefreshTimeUtc = new FYTimestamp
|
||||
{
|
||||
Seconds = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
}
|
||||
},
|
||||
RefreshHours24UtcFromBackend = 12
|
||||
RefreshHours24UtcFromBackend = 12 // Doesn't seem to work
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -74,9 +74,9 @@ public class RequestMissionCompleted : ICloudScriptFunction<FYRequestMissionComp
|
||||
} else if (reward.RewardRowHandle.RowName == "Aurum") {
|
||||
balance.HardCurrency += reward.RewardAmount;
|
||||
changedCurrencies.Add(new FYCurrencyItem { CurrencyName = "Aurum", Amount = balance.HardCurrency });
|
||||
} else if (reward.RewardRowHandle.RowName == "InsuranceToken") {
|
||||
balance.InsuranceTokens += reward.RewardAmount;
|
||||
changedCurrencies.Add(new FYCurrencyItem { CurrencyName = "InsuranceToken", Amount = balance.InsuranceTokens });
|
||||
} else if (reward.RewardRowHandle.RowName == "InsuranceCurrency") {
|
||||
balance.InsuranceCurrency += reward.RewardAmount;
|
||||
changedCurrencies.Add(new FYCurrencyItem { CurrencyName = "InsuranceCurrency", Amount = balance.InsuranceCurrency });
|
||||
} else {
|
||||
// TODO: Inventory limit check
|
||||
var blueprintData = blueprints[reward.RewardRowHandle.RowName];
|
||||
|
||||
+2
@@ -82,6 +82,7 @@ public class RequestUpdateStationInventoryFunction : ICloudScriptFunction<Reques
|
||||
// 2. Stack existing items - deletes existing items and updates amount of existing item.
|
||||
// 3. Change vanity data - updates an existing item.
|
||||
// 4. Change weapon mods - updates an existing item.
|
||||
// IMPORTANT: The same request is currently used to report the updated inventory in the end of match.
|
||||
var newInventory = new List<FYCustomItemInfo>(inventory.Count);
|
||||
foreach (var item in inventory) {
|
||||
if (!request.ItemsToRemove.Contains(item.ItemId)) {
|
||||
@@ -98,6 +99,7 @@ public class RequestUpdateStationInventoryFunction : ICloudScriptFunction<Reques
|
||||
inventoryItem.ModData = item.ModData;
|
||||
inventoryItem.PrimaryVanityId = item.PrimaryVanityId;
|
||||
inventoryItem.SecondaryVanityId = item.SecondaryVanityId;
|
||||
inventoryItem.Durability = item.Durability;
|
||||
}
|
||||
|
||||
// TODO: Check updated items and validate new items
|
||||
|
||||
@@ -117,11 +117,11 @@ public class SellItemsClientFunction : ICloudScriptFunction<SellItemsClientReque
|
||||
continue;
|
||||
}
|
||||
var blueprintData = blueprints[inventoryItem.BaseItemId];
|
||||
// TODO: Probably better to decide based on item kind instead
|
||||
if (inventoryItem.Durability == -1) {
|
||||
// NOTE: Ammo and consumables have reverse price mapping.
|
||||
if (blueprintData.Kind == "Ammo" || blueprintData.Kind == "Ability") {
|
||||
factionProgression += (int)((float)blueprintData.OverrideScrappingReputation / blueprintData.MaxAmountPerStack * inventoryItem.Amount);
|
||||
balance.SoftCurrency += (int)((float)blueprintData.OverrideScrappingReturns / blueprintData.MaxAmountPerStack * inventoryItem.Amount);
|
||||
} else {
|
||||
} else if (blueprintData.Kind == "Armor") {
|
||||
factionProgression += (int)Math.Max(
|
||||
(float)inventoryItem.Durability / blueprintData.DurabilityMax * blueprintData.OverrideScrappingReputation,
|
||||
blueprintData.OverrideScrappingReputation * blueprintData.DurabilityBrokenScrappingReturnModifier
|
||||
@@ -130,6 +130,9 @@ public class SellItemsClientFunction : ICloudScriptFunction<SellItemsClientReque
|
||||
(float)inventoryItem.Durability / blueprintData.DurabilityMax * blueprintData.OverrideScrappingReturns,
|
||||
blueprintData.OverrideScrappingReturns * blueprintData.DurabilityBrokenScrappingReturnModifier
|
||||
);
|
||||
} else {
|
||||
factionProgression += blueprintData.OverrideScrappingReputation * inventoryItem.Amount;
|
||||
balance.SoftCurrency += blueprintData.OverrideScrappingReturns * inventoryItem.Amount;
|
||||
}
|
||||
|
||||
scrappedItemIds.Add(itemId);
|
||||
|
||||
@@ -29,3 +29,30 @@ public class FYEnterMatchAzureFunction
|
||||
[JsonPropertyName("loginNonce")]
|
||||
public string LoginNonce { get; set; }
|
||||
}
|
||||
|
||||
public class FYEnterMatchAzureFunctionCB
|
||||
{
|
||||
[JsonPropertyName("optimalRegion")]
|
||||
public string? OptimalRegion { get; set; }
|
||||
|
||||
[JsonPropertyName("isMatch")]
|
||||
public bool IsMatch { get; set; }
|
||||
|
||||
[JsonPropertyName("bypassMaintenanceMode")]
|
||||
public bool BypassMaintenanceMode { get; set; }
|
||||
|
||||
[JsonPropertyName("debugOption")]
|
||||
public string? DebugOption { get; set; }
|
||||
|
||||
[JsonPropertyName("mapName")]
|
||||
public string? MapName { get; set; }
|
||||
|
||||
[JsonPropertyName("squad_id")]
|
||||
public string? SquadId { get; set; }
|
||||
|
||||
[JsonPropertyName("purchaseInsuranceRequest")]
|
||||
public FYPurchaseInventoryInsuranceRequest PurchaseInventoryInsurances { get; set; }
|
||||
|
||||
[JsonPropertyName("loginNonce")]
|
||||
public string LoginNonce { get; set; }
|
||||
}
|
||||
+27
@@ -21,3 +21,30 @@ public class FYEnterMatchmakingResult
|
||||
[JsonPropertyName("errorMessage")]
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
public class FYEnterMatchAzureFunctionResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonPropertyName("address")]
|
||||
public string? Address { get; set; }
|
||||
|
||||
[JsonPropertyName("errorMessage")]
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
[JsonPropertyName("port")]
|
||||
public int Port { get; set; }
|
||||
|
||||
[JsonPropertyName("sharedIndex")]
|
||||
public int ShardIndex { get; set; }
|
||||
|
||||
[JsonPropertyName("singleplayerStation")]
|
||||
public bool SingleplayerStation { get; set; }
|
||||
|
||||
[JsonPropertyName("maintenanceMode")]
|
||||
public bool MaintenanceMode { get; set; }
|
||||
|
||||
[JsonPropertyName("sessionTimeJoinDelay")]
|
||||
public float SessionTimeJoinDelay { get; set; }
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,9 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Prospect.Server.Api.Models.Client.Data;
|
||||
using Prospect.Server.Api.Models.Data;
|
||||
using Prospect.Server.Api.Services.CloudScript.Functions;
|
||||
using Prospect.Server.Api.Services.CloudScript.Models.Data;
|
||||
using Prospect.Server.Api.Services.Database;
|
||||
|
||||
namespace Prospect.Server.Api.Services.UserData;
|
||||
@@ -171,6 +173,25 @@ public class UserDataService
|
||||
["GlobalVanity"] = (false, $"{{\"activeGlobalVanityIds\":[\"Season03_Spray_13\",\"Banner_MIne\",\"Emote_Chill_01\",\"\",\"\",\"\",\"\",\"\"], \"droppodId\": \"VDP_Omega02\"}}"),
|
||||
["CharacterVanity"] = (false, JsonSerializer.Serialize(defaultVanity)),
|
||||
["Inventory"] = (false, "[]"),
|
||||
["JobBoardsData"] = (false, JsonSerializer.Serialize(
|
||||
new JobBoardsData {
|
||||
LastBoardRefreshTimeUtc = new CloudScript.Models.Data.FYTimestamp { Seconds = 0 },
|
||||
Boards = [
|
||||
new FYFactionContractsData {
|
||||
FactionId = "Korolev",
|
||||
Contracts = [ new FYFactionContractData { }, new FYFactionContractData { }, new FYFactionContractData { }]
|
||||
},
|
||||
new FYFactionContractsData {
|
||||
FactionId = "Osiris",
|
||||
Contracts = [ new FYFactionContractData { }, new FYFactionContractData { }, new FYFactionContractData { }]
|
||||
},
|
||||
new FYFactionContractsData {
|
||||
FactionId = "ICA",
|
||||
Contracts = [ new FYFactionContractData { }, new FYFactionContractData { }, new FYFactionContractData { }]
|
||||
}
|
||||
]
|
||||
}
|
||||
)),
|
||||
["VanityItems"] = (false, "[]"),
|
||||
["RetentionBonus"] = (false, "{\"claimedAll\":false,\"daysClaimed\":0,\"lastClaimTime\":{\"seconds\":0}}"),
|
||||
["Balance"] = (false, "{\"IN\": 0,\"AU\": 800,\"SC\": 30000}"),
|
||||
|
||||
+26
-26
@@ -21,7 +21,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prospect.Unreal.Tests", "Pr
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prospect.Unreal.Generator", "Prospect.Unreal.Generator\Prospect.Unreal.Generator.csproj", "{71E3E262-49C5-4B6C-9670-D0741CEDB63B}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Prospect.Client.Loader", "Prospect.Client.Loader\Prospect.Client.Loader.vcxproj", "{8170A17F-E536-4FDB-BC01-78BF09A9A48B}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Prospect.Client.Loader", "Prospect.Client.Loader\Prospect.Client.Loader.vcxproj", "{04B1D963-2BCB-4887-9D6D-04CB3366A348}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -183,30 +183,30 @@ Global
|
||||
{71E3E262-49C5-4B6C-9670-D0741CEDB63B}.Season 3 Release|x64.Build.0 = Season 3 Release|Any CPU
|
||||
{71E3E262-49C5-4B6C-9670-D0741CEDB63B}.Season 3 Release|x86.ActiveCfg = Season 3 Release|Any CPU
|
||||
{71E3E262-49C5-4B6C-9670-D0741CEDB63B}.Season 3 Release|x86.Build.0 = Season 3 Release|Any CPU
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Debug|x64.Build.0 = Debug|x64
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Debug|x86.Build.0 = Debug|Win32
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Release|x64.ActiveCfg = Release|x64
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Release|x64.Build.0 = Release|x64
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Release|x86.ActiveCfg = Release|Win32
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Release|x86.Build.0 = Release|Win32
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 2 Debug|x64.ActiveCfg = Debug|x64
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 2 Debug|x64.Build.0 = Debug|x64
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 2 Debug|x86.ActiveCfg = Debug|Win32
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 2 Debug|x86.Build.0 = Debug|Win32
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 2 Release|x64.ActiveCfg = Release|x64
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 2 Release|x64.Build.0 = Release|x64
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 2 Release|x86.ActiveCfg = Release|Win32
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 2 Release|x86.Build.0 = Release|Win32
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 3 Debug|x64.ActiveCfg = Debug|x64
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 3 Debug|x64.Build.0 = Debug|x64
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 3 Debug|x86.ActiveCfg = Debug|Win32
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 3 Debug|x86.Build.0 = Debug|Win32
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 3 Release|x64.ActiveCfg = Release|x64
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 3 Release|x64.Build.0 = Release|x64
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 3 Release|x86.ActiveCfg = Release|Win32
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B}.Season 3 Release|x86.Build.0 = Release|Win32
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Debug|x64.Build.0 = Debug|x64
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Debug|x86.Build.0 = Debug|Win32
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Release|x64.ActiveCfg = Release|x64
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Release|x64.Build.0 = Release|x64
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Release|x86.ActiveCfg = Release|Win32
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Release|x86.Build.0 = Release|Win32
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 2 Debug|x64.ActiveCfg = Debug|x64
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 2 Debug|x64.Build.0 = Debug|x64
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 2 Debug|x86.ActiveCfg = Debug|Win32
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 2 Debug|x86.Build.0 = Debug|Win32
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 2 Release|x64.ActiveCfg = Release|x64
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 2 Release|x64.Build.0 = Release|x64
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 2 Release|x86.ActiveCfg = Release|Win32
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 2 Release|x86.Build.0 = Release|Win32
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 3 Debug|x64.ActiveCfg = Debug|x64
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 3 Debug|x64.Build.0 = Debug|x64
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 3 Debug|x86.ActiveCfg = Debug|Win32
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 3 Debug|x86.Build.0 = Debug|Win32
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 3 Release|x64.ActiveCfg = Release|x64
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 3 Release|x64.Build.0 = Release|x64
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 3 Release|x86.ActiveCfg = Release|Win32
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348}.Season 3 Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -218,7 +218,7 @@ Global
|
||||
{380DD490-8F01-4025-91D7-74C289AA5B75} = {76364BAA-BC9D-4A92-AA2A-8914FAE22834}
|
||||
{14DAE1A4-6F3F-43C0-AC17-9DCDE420AF34} = {168B6247-2D33-4942-AB3F-C5041B3BDFEA}
|
||||
{71E3E262-49C5-4B6C-9670-D0741CEDB63B} = {76364BAA-BC9D-4A92-AA2A-8914FAE22834}
|
||||
{8170A17F-E536-4FDB-BC01-78BF09A9A48B} = {31D3D5D2-B011-4F9B-B6CC-D0A35675D797}
|
||||
{04B1D963-2BCB-4887-9D6D-04CB3366A348} = {31D3D5D2-B011-4F9B-B6CC-D0A35675D797}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {12888C62-D3A2-4DD7-A9A5-1481D95865D0}
|
||||
|
||||
Reference in New Issue
Block a user