diff --git a/components/candle/masteryService.ts b/components/candle/masteryService.ts index 69fbad8e8..a216a3cae 100644 --- a/components/candle/masteryService.ts +++ b/components/candle/masteryService.ts @@ -21,7 +21,7 @@ import { getSubLocationByName, } from "../contracts/dataGen" import { log, LogLevel } from "../loggingInterop" -import { getVersionedConfig } from "../configSwizzleManager" +import { getConfig } from "../configSwizzleManager" import { getUserData } from "../databaseHandler" import { LocationMasteryData, @@ -265,12 +265,9 @@ export class MasteryService { // TODO: Refactor this into the new inventory system? const name = isSniper - ? getVersionedConfig( - "SniperUnlockables", - gameVersion, - false, - ).find((unlockable) => unlockable.Id === subPackageId)?.Properties - .Name + ? getConfig("SniperUnlockables", false).find( + (unlockable) => unlockable.Id === subPackageId, + )?.Properties.Name : undefined return { diff --git a/components/cli.ts b/components/cli.ts index b39ba8b32..997413ae5 100644 --- a/components/cli.ts +++ b/components/cli.ts @@ -30,11 +30,14 @@ import { readFileSync, writeFileSync } from "fs" import { pack, unpack } from "msgpackr" import { log, LogLevel } from "./loggingInterop" import { startServer } from "./index" +import { PEACOCKVERSTRING } from "./utils" +import * as process from "node:process" program.description( "The Peacock Project is a HITMAN™ World of Assassination Trilogy server replacement.", ) +program.option("-v, --version", "print the version number and exit") program.option( "--hmr", "enable experimental hot reloading of contracts", @@ -45,9 +48,22 @@ program.option( "activate plugin development features - requires plugin dev workspace setup", getFlag("developmentPluginDevHost") as boolean, ) -program.action(startServer) +program.action( + (options: { hmr: boolean; pluginDevHost: boolean; version: boolean }) => { + if (options.version) { + console.log(`Peacock ${PEACOCKVERSTRING}`) + return process.exit() + } -program.command("tools").description("open the tools UI").action(toolsMenu) + return startServer(options) + }, +) + +program + .command("tools") + .description("open the tools UI") + .option("-s, --skip", "Skip encrypting the debug profile", false) + .action(toolsMenu) // noinspection RequiredAttributes program diff --git a/components/contracts/dataGen.ts b/components/contracts/dataGen.ts index 1de792cad..f2eebf3e5 100644 --- a/components/contracts/dataGen.ts +++ b/components/contracts/dataGen.ts @@ -755,6 +755,84 @@ const noPacifications = { ], } +const hideAllBodies = { + IsCompleted: true, + ContractConditionType: "PrimarySecondary", + Primary: [ + { + Type: "gamechanger", + Properties: { + Id: "c2da52c5-ff3e-41cd-a175-4ed9267f6c95", + Name: "UI_GAMECHANGERS_GLOBAL_CONTRACTCONDITION_HIDE_ALL_BODIES_PRIMARY_NAME", + Description: + "UI_GAMECHANGERS_GLOBAL_CONTRACTCONDITION_HIDE_ALL_BODIES_PRIMARY_DESC", + LongDescription: + "UI_GAMECHANGERS_GLOBAL_CONTRACTCONDITION_HIDE_ALL_BODIES_PRIMARY_DESC", + TileImage: + "images/contractconditions/condition_contrac_hide_all_bodies.jpg", + Icon: "images/challenges/default_challenge_icon.png", + ObjectivesCategory: "primary", + }, + }, + ], + Secondary: [ + { + Type: "gamechanger", + Properties: { + Id: "a77cf01e-ab02-4b1c-a4bd-a37fb8be1114", + Name: "UI_GAMECHANGERS_GLOBAL_CONTRACTCONDITION_HIDE_ALL_BODIES_SECONDARY_NAME", + Description: + "UI_GAMECHANGERS_GLOBAL_CONTRACTCONDITION_HIDE_ALL_BODIES_SECONDARY_DESC", + LongDescription: + "UI_GAMECHANGERS_GLOBAL_CONTRACTCONDITION_HIDE_ALL_BODIES_SECONDARY_DESC", + TileImage: + "images/contractconditions/condition_contrac_hide_all_bodies.jpg", + Icon: "images/challenges/default_challenge_icon.png", + ObjectivesCategory: "secondary", + }, + }, + ], +} + +const doNotGetSpotted = { + IsCompleted: true, + ContractConditionType: "PrimarySecondary", + Primary: [ + { + Type: "gamechanger", + Properties: { + Id: "9f409781-0a06-4748-b08d-784e78c6d481", + Name: "UI_GAMECHANGERS_GLOBAL_CONTRACTCONDITION_DO_NOT_GET_SPOTTED_PRIMARY_NAME", + Description: + "UI_GAMECHANGERS_GLOBAL_CONTRACTCONDITION_DO_NOT_GET_SPOTTED_PRIMARY_DESC", + LongDescription: + "UI_GAMECHANGERS_GLOBAL_CONTRACTCONDITION_DO_NOT_GET_SPOTTED_PRIMARY_DESC", + TileImage: + "images/contractconditions/condition_contrac_do_not_be_spotted.jpg", + Icon: "images/challenges/default_challenge_icon.png", + ObjectivesCategory: "primary", + }, + }, + ], + Secondary: [ + { + Type: "gamechanger", + Properties: { + Id: "a77cf01e-ab02-4b1c-a4bd-a37fb8be1114", + Name: "UI_GAMECHANGERS_GLOBAL_CONTRACTCONDITION_DO_NOT_GET_SPOTTED_SECONDARY_NAME", + Description: + "UI_GAMECHANGERS_GLOBAL_CONTRACTCONDITION_DO_NOT_GET_SPOTTED_SECONDARY_DESC", + LongDescription: + "UI_GAMECHANGERS_GLOBAL_CONTRACTCONDITION_DO_NOT_GET_SPOTTED_SECONDARY_DESC", + TileImage: + "images/contractconditions/condition_contrac_do_not_be_spotted.jpg", + Icon: "images/challenges/default_challenge_icon.png", + ObjectivesCategory: "secondary", + }, + }, + ], +} + export function complications(timeString: string) { return [ { @@ -831,5 +909,7 @@ export function complications(timeString: string) { noMissedShots, headshotsOnly, targetsOnly, + hideAllBodies, + doNotGetSpotted, ] } diff --git a/components/tools.ts b/components/tools.ts index cff92e250..474b53d23 100644 --- a/components/tools.ts +++ b/components/tools.ts @@ -33,6 +33,8 @@ import picocolors from "picocolors" import { Filename, npath, PortablePath, ppath, xfs } from "@yarnpkg/fslib" import { makeEmptyArchive, ZipFS } from "@yarnpkg/libzip" import { configs } from "./configSwizzleManager" +import * as crypto from "node:crypto" +import * as fs from "node:fs" const IMAGE_PACK_REPO = "thepeacockproject/ImagePack" @@ -45,7 +47,7 @@ const modFrameworkDataPath: string | false = )) || false -export async function toolsMenu() { +export async function toolsMenu(options: { skip: boolean }) { const init = await prompts({ name: "actions", message: "Select actions:", @@ -72,7 +74,7 @@ export async function toolsMenu() { switch (init.actions) { case "debug": - await exportDebugInfo() + await exportDebugInfo(options.skip) break case "download-images": await downloadImagePack() @@ -93,7 +95,7 @@ async function copyIntoZip(zip: ZipFS, path: string): Promise { }) } -async function exportDebugInfo(): Promise { +async function exportDebugInfo(skipEncrypt: boolean): Promise { const cpus = cpuList().map((cpu, index) => ({ core: index + 1, ...cpu, @@ -151,6 +153,76 @@ async function exportDebugInfo(): Promise { zip.saveAndClose() + encrypt: if (!skipEncrypt) { + const publicKey = crypto.createPublicKey( + "-----BEGIN PUBLIC KEY-----\n" + + "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAupOzd1PiMy8M+hWRwt3J\n" + + "/fZPExUwRVlGTd4goZSfrZO2NfXjCzkfTvAIWe8ItU88oUsbfcMu/iQ7JqtoeMdc\n" + + "yd4/hA0Glnc10fREnFJrJTEhetzrv1PUXx8uSYzkfj6L8eO8UO6W/faomYdNontQ\n" + + "F3CEBUvYHXRo31D7ubQKiAXExDLybWCZXDV4f0HL1vaTR0gjB7tCfq5D9jpdaBR5\n" + + "sQPX2RSexUJE4dn5t4Gkbl8G9CaSEwPaBIAhcHnY8BkrNb16cNTp24AUqeKC1AMI\n" + + "e1xgOLjkx0EG+43dGX48lP8oRAixyemA6QDcAwufPPxuCbAVNMN7+NpTEaky2j+U\n" + + "Gay80XtTOAbsW8kgRVUJnw6CbaHvvmDUL19q7rGAe8dOgCiatE1HWpxPCZX8KBrw\n" + + "iCIEmPzftqqulqwmfh1Uf7ZWvdb9ugYhp9wce0cGX1Lb6uO4gd+GAJsgyGHa/ny9\n" + + "Y8nqGHpiCcBXMgZ8ggSQXQlLpJGmGfkxNyw8RRM1uiBBU9y9QxhcghAhkkS814a0\n" + + "F3UAISYursYS337uhm54qvLNKDIHqaOpZHh23El092zQYJlZbCZn5WZiTtBum1Us\n" + + "XvG7eLb7Tulqnk90p0SRhQeIt2zGnb49Zq+ixP9YJX7LK3xm+JJ9j6/1oKJbrdBL\n" + + "1ppbcSy1RGsiYbPT3ohZ59sCAwEAAQ==\n" + + "-----END PUBLIC KEY-----", + ) + + const options: { + algorithm: string + key: Buffer | string + iv: Buffer | string + } = { + algorithm: "aes-256-cbc", + key: crypto.randomBytes(32), + iv: crypto.randomBytes(16), + } + + const cipher = crypto.createCipheriv( + options.algorithm, + options.key, + options.iv, + ) + + options.key = options.key.toString("base64") + options.iv = options.iv.toString("base64") + + const buffer = fs.readFileSync("DEBUG_PROFILE.zip") + const encrypted = Buffer.concat([cipher.update(buffer), cipher.final()]) + + if (!encrypted) { + log(LogLevel.WARN, "Failed to encrypt debug profile!") + break encrypt + } + + fs.rmSync("DEBUG_PROFILE.zip") + + const zipFile = ppath.resolve(ppath.cwd(), "DEBUG_PROFILE.zip") + + // we'll start by creating an empty zip file + await writeFile(npath.fromPortablePath(zipFile), makeEmptyArchive()) + + const zip = new ZipFS(zipFile, { create: true }) + + await zip.writeFilePromise( + zip.resolve("DEBUG_PROFILE.peacock" as Filename), + encrypted, + ) + + await zip.writeFilePromise( + zip.resolve("key" as Filename), + crypto.publicEncrypt( + publicKey, + Buffer.from(JSON.stringify(options)), + ), + ) + + zip.saveAndClose() + } + log( LogLevel.INFO, "Successfully outputted debugging data to DEBUG_PROFILE.zip!", diff --git a/components/webFeatures.ts b/components/webFeatures.ts index af16f157e..1ea2e3fc1 100644 --- a/components/webFeatures.ts +++ b/components/webFeatures.ts @@ -64,9 +64,9 @@ if (PEACOCK_DEV) { res.set("Access-Control-Allow-Origin", "*") res.set( "Access-Control-Allow-Methods", - "GET,HEAD,PUT,PATCH,POST,DELETE", + "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS", ) - res.set("Access-Control-Allow-Headers", "Content-Type") + res.set("Access-Control-Allow-Headers", "content-type") next() }) } @@ -357,6 +357,8 @@ webFeaturesRouter.post( try { // Challenge Progression + log(LogLevel.DEBUG, "Getting challenge progression...") + const challengeProgression = await auth._useService< ChallengeProgressionData[] >( @@ -387,6 +389,8 @@ webFeaturesRouter.post( ) // Profile Progression + log(LogLevel.DEBUG, "Getting profile progression...") + const exts = await auth._useService( `https://${remoteService}.hitman.io/authentication/api/userchannel/ProfileService/GetProfile`, false, @@ -405,6 +409,8 @@ webFeaturesRouter.post( ) if (req.query.gv !== "h1") { + log(LogLevel.DEBUG, "Processing PlayerProfileXP...") + const sublocations = exts.data.Extensions.progression .PlayerProfileXP .Sublocations as unknown as OfficialSublocation[] @@ -427,32 +433,39 @@ webFeaturesRouter.post( ), } + log(LogLevel.DEBUG, "Processing opportunity progression...") + userdata.Extensions.opportunityprogression = Object.fromEntries( Object.keys( - exts.data.Extensions.opportunityprogression, + exts.data.Extensions.opportunityprogression || {}, ).map((value) => [value, true]), ) - for (const [unlockId, data] of Object.entries( - exts.data.Extensions.progression.Unlockables ?? {}, - )) { - const unlockableId = unlockId.toUpperCase() - - if (!(unlockableId in SNIPER_UNLOCK_TO_LOCATION)) continue - ;( - userdata.Extensions.progression.Locations[ - SNIPER_UNLOCK_TO_LOCATION[unlockableId] - ] as SubPackageData - )[unlockableId] = { - Xp: data.Xp, - Level: data.Level, - PreviouslySeenXp: data.PreviouslySeenXp, + if (exts.data.Extensions.progression.Unlockables) { + log(LogLevel.DEBUG, "Processing unlockables...") + + for (const [unlockId, data] of Object.entries( + exts.data.Extensions.progression.Unlockables, + )) { + const unlockableId = unlockId.toUpperCase() + + if (!(unlockableId in SNIPER_UNLOCK_TO_LOCATION)) + continue + ;( + userdata.Extensions.progression.Locations[ + SNIPER_UNLOCK_TO_LOCATION[unlockableId] + ] as SubPackageData + )[unlockableId] = { + Xp: data.Xp, + Level: data.Level, + PreviouslySeenXp: data.PreviouslySeenXp, + } } } } userdata.Extensions.gamepersistentdata = - exts.data.Extensions.gamepersistentdata + exts.data.Extensions.gamepersistentdata || {} const sublocations = getSublocations(req.query.gv) userdata.Extensions.defaultloadout ??= {} @@ -467,7 +480,8 @@ webFeaturesRouter.post( } } - userdata.Extensions.achievements = exts.data.Extensions.achievements + userdata.Extensions.achievements = + exts.data.Extensions.achievements || [] for (const [locId, data] of Object.entries( exts.data.Extensions.progression.Locations, @@ -508,6 +522,11 @@ webFeaturesRouter.post( } // Escalation & Arcade Progression + log( + LogLevel.DEBUG, + `Getting escalation${req.query.gv === "h3" ? " & arcade" : ""} progression...`, + ) + const escalations = await getAllHitsCategory( auth, remoteService!, @@ -546,6 +565,8 @@ webFeaturesRouter.post( // TODO: Try and see if there is a less intensive way to do this // GetForPlay2 is quite intensive on IOI's side as it starts a session if (req.query.gv === "h3") { + log(LogLevel.DEBUG, "Getting freelancer progression...") + await auth._useService( `https://${remoteService}.hitman.io/authentication/api/configuration/Init?configName=pc-prod&lockedContentDisabled=false&isFreePrologueUser=false&isIntroPackUser=false&isFullExperienceUser=true`, true, diff --git a/package.json b/package.json index b46114b8b..1dde88038 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@peacockproject/monorepo", - "version": "7.2.0", + "version": "7.2.1", "private": true, "license": "AGPL-3.0", "scripts": { diff --git a/static/allunlockables.json b/static/allunlockables.json index 69fc6914e..758af286b 100644 --- a/static/allunlockables.json +++ b/static/allunlockables.json @@ -28214,7 +28214,8 @@ "Quality": 3, "LoadoutSlot": "gear", "Rarity": "common", - "RepositoryId": "09268e60-216a-4ccf-8e41-c3cc45391222" + "RepositoryId": "09268e60-216a-4ccf-8e41-c3cc45391222", + "ItemSize": "ITEMSIZE_SMALL" }, "Rarity": "common" }, @@ -28235,7 +28236,8 @@ "Quality": 2, "LoadoutSlot": "gear", "Rarity": "common", - "RepositoryId": "6d4c88f3-9a09-453c-9a6e-a081f1136bf3" + "RepositoryId": "6d4c88f3-9a09-453c-9a6e-a081f1136bf3", + "ItemSize": "ITEMSIZE_SMALL" }, "Rarity": "common" }, @@ -28256,7 +28258,8 @@ "Quality": 4, "Rarity": "common", "LoadoutSlot": "carriedweapon", - "RepositoryId": "8598ae82-53ac-43ba-9f43-30140d6ba7ee" + "RepositoryId": "8598ae82-53ac-43ba-9f43-30140d6ba7ee", + "ItemSize": "ITEMSIZE_LARGE" }, "Rarity": "common" }, @@ -28280,7 +28283,8 @@ "RepositoryId": "00d1f430-1192-4562-be0f-5538b6d0c575", "UnlockOrder": 15, "AllowUpSync": true, - "Entitlements": ["H3_ET_SAMBUCA"] + "Entitlements": ["H3_ET_SAMBUCA"], + "ItemSize": "ITEMSIZE_SMALL" }, "Rarity": "common" }, @@ -28303,7 +28307,8 @@ "Rarity": "common", "RepositoryId": "7bfd6433-fd6e-4b82-8745-ee32c305d471", "AllowUpSync": true, - "Entitlements": ["H3_ET_SAMBUCA"] + "Entitlements": ["H3_ET_SAMBUCA"], + "ItemSize": "ITEMSIZE_SMALL" }, "Rarity": "common" }, @@ -28326,7 +28331,8 @@ "Rarity": "common", "RepositoryId": "bc1bd133-f6f6-4cee-ba73-bc1881864b22", "AllowUpSync": true, - "Entitlements": ["H3_ET_SAMBUCA"] + "Entitlements": ["H3_ET_SAMBUCA"], + "ItemSize": "ITEMSIZE_SMALL" }, "Rarity": "common" }, @@ -28347,7 +28353,8 @@ "Quality": 3, "LoadoutSlot": "gear", "Rarity": "common", - "RepositoryId": "e68927af-746d-41fa-83e2-402da0163262" + "RepositoryId": "e68927af-746d-41fa-83e2-402da0163262", + "ItemSize": "ITEMSIZE_SMALL" }, "Rarity": "common" }