Repository: marcincichocki/breach-protocol-autosolver
Pull request #370 (2023-09-27 21:04:36)

feat: add support for patch 2.0


Patch:
diff --git a/src/core/daemon-set.ts b/src/core/daemon-set.ts
new file mode 100644
index 0000000..ead8109
--- /dev/null
+++ b/src/core/daemon-set.ts
@@ -0,0 +1,47 @@
+import { DaemonId } from './common';
+import {
+  DAEMON_ADVANCED_DATAMINE,
+  DAEMON_BASIC_DATAMINE,
+  DAEMON_CAMERA_SHUTDOWN,
+  DAEMON_DATAMINE_COPY_MALWARE,
+  DAEMON_DATAMINE_CRAFTING_SPECS,
+  DAEMON_DATAMINE_V1,
+  DAEMON_DATAMINE_V2,
+  DAEMON_DATAMINE_V3,
+  DAEMON_EXPERT_DATAMINE,
+  DAEMON_FRIENDLY_TURRETS,
+  DAEMON_GAIN_ACCESS,
+  DAEMON_ICEPICK,
+  DAEMON_MASS_VULNERABILITY,
+  DAEMON_NEUTRALIZE_MALWARE,
+  DAEMON_OPTICS_JAMMER,
+  DAEMON_TURRET_SHUTDOWN,
+  DAEMON_WEAPONS_JAMMER,
+} from './daemons';
+
+export const LEGACY_DAEMONS = new Set<DaemonId>([
+  DAEMON_DATAMINE_V1,
+  DAEMON_DATAMINE_V2,
+  DAEMON_DATAMINE_V3,
+  DAEMON_ICEPICK,
+  DAEMON_MASS_VULNERABILITY,
+  DAEMON_CAMERA_SHUTDOWN,
+  DAEMON_FRIENDLY_TURRETS,
+  DAEMON_TURRET_SHUTDOWN,
+  DAEMON_OPTICS_JAMMER,
+  DAEMON_WEAPONS_JAMMER,
+  DAEMON_DATAMINE_COPY_MALWARE,
+  DAEMON_NEUTRALIZE_MALWARE,
+  DAEMON_GAIN_ACCESS,
+  DAEMON_DATAMINE_CRAFTING_SPECS,
+]);
+
+export const DAEMONS = new Set<DaemonId>([
+  DAEMON_DATAMINE_COPY_MALWARE,
+  DAEMON_NEUTRALIZE_MALWARE,
+  DAEMON_GAIN_ACCESS,
+  DAEMON_DATAMINE_CRAFTING_SPECS,
+  DAEMON_BASIC_DATAMINE,
+  DAEMON_ADVANCED_DATAMINE,
+  DAEMON_EXPERT_DATAMINE,
+]);
diff --git a/src/core/daemons-i18n.ts b/src/core/daemons-i18n.ts
index 7bf2e7b..4adeb40 100644
--- a/src/core/daemons-i18n.ts
+++ b/src/core/daemons-i18n.ts
@@ -37,6 +37,9 @@ export const eng: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: 'NEUTRALIZE MALWARE',
   [d.DAEMON_GAIN_ACCESS]: 'GAIN ACCESS',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'DATAMINE: CRAFTING SPECS',
+  [d.DAEMON_BASIC_DATAMINE]: 'BASIC DATAMINE',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'ADVANCED DATAMINE',
+  [d.DAEMON_EXPERT_DATAMINE]: 'EXPERT DATAMINE',
 };
 
 export const pol: DaemonDict = {
@@ -56,6 +59,9 @@ export const pol: DaemonDict = {
   [d.DAEMON_GAIN_ACCESS]: 'UZYSKAJ DOSTĘP',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]:
     'EKSPLORACJA DANYCH: SCHEMATY WYTWARZANIA',
+  [d.DAEMON_BASIC_DATAMINE]: 'EKSPLORACJA: DANE BAZOWE',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'EKSPLORACJA: DANE SPECJALNE',
+  [d.DAEMON_EXPERT_DATAMINE]: 'EKSPLORACJA: DANE WRAŻLIWE',
 };
 
 export const rus: DaemonDict = {
@@ -73,6 +79,9 @@ export const rus: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: 'ОБЕЗВРЕДИТЬ ВИРУС',
   [d.DAEMON_GAIN_ACCESS]: 'ПОЛУЧИТЬ ДОСТУП',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'ДОБЫЧА ДАННЫХ: ДОКУМЕНТАЦИЯ',
+  [d.DAEMON_BASIC_DATAMINE]: 'ОБЫЧНА ДОБЫЧА ДАННЫХ',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'ПРОДВИНУТАЯ ДОБЫЧА ДАННЫХ',
+  [d.DAEMON_EXPERT_DATAMINE]: 'ВЫСОКОУРОВНЕВАЯ ДОБЫЧА ДАННЫХ',
 };
 
 export const deu: DaemonDict = {
@@ -90,6 +99,9 @@ export const deu: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: 'MALWARE NEUTRALISIEREN',
   [d.DAEMON_GAIN_ACCESS]: 'ZUGRIFF VERSCHAFFEN',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'DATA-MINING: SCHEMA',
+  [d.DAEMON_BASIC_DATAMINE]: 'EINFACHES DATA-MINING',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'VERBESSERTES DATA-MINING',
+  [d.DAEMON_EXPERT_DATAMINE]: 'HERAUSRAGENDES DATA-MINING',
 };
 
 export const fra: DaemonDict = {
@@ -107,6 +119,9 @@ export const fra: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: 'NEUTRALISER LE MALWARE',
   [d.DAEMON_GAIN_ACCESS]: "OBTENIR L'ACCÈS",
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'DATA MINING : PLAN DE FABRICATION',
+  [d.DAEMON_BASIC_DATAMINE]: 'DATA MINING ÉLÉMENTAIRE',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'DATA MINING AVANCÉ',
+  [d.DAEMON_EXPERT_DATAMINE]: 'DATA MINING EXPERT',
 };
 
 export const por: DaemonDict = {
@@ -125,6 +140,9 @@ export const por: DaemonDict = {
   [d.DAEMON_GAIN_ACCESS]: 'OBTER ACESSO',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]:
     'MINERAÇÃO DE DADOS: ESPECIALIZAÇÕES DE CRIAÇÃO',
+  [d.DAEMON_BASIC_DATAMINE]: 'MINERAÇÃO DE DADOS BÁSICA',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'MINERAÇÃO DE DADOS AVANÇADA',
+  [d.DAEMON_EXPERT_DATAMINE]: 'PERÍCIA EM MINERAÇÃO DE DADOS',
 };
 
 export const chi_sim: DaemonDict = {
@@ -142,6 +160,9 @@ export const chi_sim: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: '清除恶意软件',
   [d.DAEMON_GAIN_ACCESS]: '获取权限',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: '数据挖掘: 制作规范',
+  [d.DAEMON_BASIC_DATAMINE]: '基础数据挖掘',
+  [d.DAEMON_ADVANCED_DATAMINE]: '高级数据挖掘',
+  [d.DAEMON_EXPERT_DATAMINE]: '专家数据挖掘',
 };
 
 export const spa: DaemonDict = {
@@ -160,6 +181,9 @@ export const spa: DaemonDict = {
   [d.DAEMON_GAIN_ACCESS]: 'OBTENER ACCESO',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]:
     'MINERÍA DE DATOS: ESPECIFICACIONES DE CREACIÓN',
+  [d.DAEMON_BASIC_DATAMINE]: 'MINERÍA DE DATOS BÁSICA',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'MINERÍA DE DATOS AVANZADA',
+  [d.DAEMON_EXPERT_DATAMINE]: 'MINERÍA DE DATOS EXPERTA',
 };
 
 export const ita: DaemonDict = {
@@ -178,6 +202,9 @@ export const ita: DaemonDict = {
   [d.DAEMON_GAIN_ACCESS]: 'OTTIENI ACCESSO',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]:
     'ESTRAZIONE DATI: ISTRUZIONI DI CREAZIONE',
+  [d.DAEMON_BASIC_DATAMINE]: 'ESTRAZIONE DATI SEMPLICE',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'ESTRAZIONE DATI AVANZATA',
+  [d.DAEMON_EXPERT_DATAMINE]: 'ESTRAZIONE DATI ESPERTO',
 };
 
 export const kor: DaemonDict = {
@@ -195,6 +222,9 @@ export const kor: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: '멀웨어 무력화',
   [d.DAEMON_GAIN_ACCESS]: '액세스 권한 획득',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: '데이터마이닝: 제작 사양서',
+  [d.DAEMON_BASIC_DATAMINE]: '기본 데이터마이닝',
+  [d.DAEMON_ADVANCED_DATAMINE]: '고급 데이터마이닝',
+  [d.DAEMON_EXPERT_DATAMINE]: '전문 데이터마이닝',
 };
 
 export const jpn: DaemonDict = {
@@ -212,6 +242,9 @@ export const jpn: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: 'マルウェアを無力化する',
   [d.DAEMON_GAIN_ACCESS]: 'アクセス取得',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'データマイニング: クラフト用設計図',
+  [d.DAEMON_BASIC_DATAMINE]: '基礎データマイニング',
+  [d.DAEMON_ADVANCED_DATAMINE]: '高度データマイニング',
+  [d.DAEMON_EXPERT_DATAMINE]: '超高度データマイニング',
 };
 
 export const tha: DaemonDict = {
@@ -229,6 +262,9 @@ export const tha: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: 'กําจัดมัลแวร์',
   [d.DAEMON_GAIN_ACCESS]: 'ขอสิทธิ์เข้าถึง',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'ดาต้าไมน์: สูตรการคราฟต์',
+  [d.DAEMON_BASIC_DATAMINE]: 'ดาต้าไมน์ขันพื่นฐาน',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'ดาต้าไมน์ขันสูง',
+  [d.DAEMON_EXPERT_DATAMINE]: 'ดาต้าไมน์ขันเชียวชาญ',
 };
 
 export const tur: DaemonDict = {
@@ -246,6 +282,9 @@ export const tur: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: 'KÖTÜ AMAÇLI YAZILIMI ORTADAN KALDIR',
   [d.DAEMON_GAIN_ACCESS]: 'ERİŞİM SAĞLA',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'VERİ MADENİ: ÜRETİM KILAVUZLARI',
+  [d.DAEMON_BASIC_DATAMINE]: 'BASİT VERİ MADENİ',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'GELİŞMİŞ VERİ MADENİ',
+  [d.DAEMON_EXPERT_DATAMINE]: 'UZMAN VERİ MADENİ',
 };
 
 export const ara: DaemonDict = {
@@ -263,6 +302,9 @@ export const ara: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: 'اقض على البرنامج الخبيث',
   [d.DAEMON_GAIN_ACCESS]: 'تمكن من الوصول',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'منجم بيانات: مواصفات التصنيع',
+  [d.DAEMON_BASIC_DATAMINE]: 'منجه بيانات أساسي',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'منجم بيانات متقدم',
+  [d.DAEMON_EXPERT_DATAMINE]: 'منجم بيانات نخبوي',
 };
 
 export const chi_tra: DaemonDict = {
@@ -280,6 +322,9 @@ export const chi_tra: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: '制止惡意軟體',
   [d.DAEMON_GAIN_ACCESS]: '取得存取權',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: '資料探勘 : 製作需求',
+  [d.DAEMON_BASIC_DATAMINE]: '基礎資料探勘',
+  [d.DAEMON_ADVANCED_DATAMINE]: '進階資料探勘',
+  [d.DAEMON_EXPERT_DATAMINE]: '專業資料探勘',
 };
 
 export const ces: DaemonDict = {
@@ -297,6 +342,9 @@ export const ces: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: 'ZNEŠKODNIT MALWARE',
   [d.DAEMON_GAIN_ACCESS]: 'ZÍSKAT PŘÍSTUP',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'TĚŽBA DAT: VÝROBNÍ SPECIFIKACE',
+  [d.DAEMON_BASIC_DATAMINE]: 'ZÁKLADNÍ TĚŽBA DAT',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'POKROČILÁ TĚŽBA DAT',
+  [d.DAEMON_EXPERT_DATAMINE]: 'EXPERTNÍ TĚŽBA DAT',
 };
 
 export const hun: DaemonDict = {
@@ -315,6 +363,9 @@ export const hun: DaemonDict = {
   [d.DAEMON_NEUTRALIZE_MALWARE]: 'ROSSZINDULATÚ SZOFTVER HATÁSTALANÍTÁSA',
   [d.DAEMON_GAIN_ACCESS]: 'HOZZÁFÉRÉS MEGSZERZÉSE',
   [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'ADATBÁNYÁSZAT: TÁRGYKÉSZÍTÉSI ÁBRA',
+  [d.DAEMON_BASIC_DATAMINE]: 'ALAP ADATBÁNYÁSZAT',
+  [d.DAEMON_ADVANCED_DATAMINE]: 'HALADÓ ADATBÁNYÁSZAT',
+  [d.DAEMON_EXPERT_DATAMINE]: 'SZAKÉRTŐ ADATBÁNYÁSZAT',
 };
 
 export const daemonsI18n: Record<BreachProtocolLanguage, DaemonDict> = {
diff --git a/src/core/daemons.ts b/src/core/daemons.ts
index 7efe50f..d0cb820 100644
--- a/src/core/daemons.ts
+++ b/src/core/daemons.ts
@@ -16,3 +16,9 @@ export const DAEMON_DATAMINE_COPY_MALWARE = 'DAEMON_DATAMINE_COPY_MALWARE';
 export const DAEMON_NEUTRALIZE_MALWARE = 'DAEMON_NEUTRALIZE_MALWARE';
 export const DAEMON_GAIN_ACCESS = 'DAEMON_GAIN_ACCESS';
 export const DAEMON_DATAMINE_CRAFTING_SPECS = 'DAEMON_DATAMINE_CRAFTING_SPECS';
+// Patch 2.0 daemons.
+export const DAEMON_BASIC_DATAMINE = 'DAEMON_BASIC_DATAMINE';
+export const DAEMON_ADVANCED_DATAMINE = 'DAEMON_ADVANCED_DATAMINE';
+export const DAEMON_EXPERT_DATAMINE = 'DAEMON_EXPERT_DATAMINE';
+// This daemon is only used inside breach protocol tutorial.
+// export const DAEMON_EXTRACT_EURODOLLARS = 'DAEMON_EXTRACT_EURODOLLARS';
diff --git a/src/core/ocr/fragments/base.ts b/src/core/ocr/fragments/base.ts
index 3ec3fd5..5ef55bb 100644
--- a/src/core/ocr/fragments/base.ts
+++ b/src/core/ocr/fragments/base.ts
@@ -43,6 +43,7 @@ export type BreachProtocolFragmentResults = [
 
 export interface BreachProtocolFragmentOptions extends FragmentOptions {
   recognizer?: BreachProtocolRecognizer;
+  patch: '1.x' | '2.x';
 }
 
 export abstract class BreachProtocolFragment<
diff --git a/src/core/ocr/fragments/types-fragment.ts b/src/core/ocr/fragments/types-fragment.ts
index ea4abb0..1e955a6 100644
--- a/src/core/ocr/fragments/types-fragment.ts
+++ b/src/core/ocr/fragments/types-fragment.ts
@@ -1,10 +1,11 @@
 import { Point, similarity, unique } from '@/common';
 import {
-  DaemonId,
   DAEMONS_SIZE_MAX,
   DAEMONS_SIZE_MIN,
+  DaemonId,
   TypesRawData,
 } from '../../common';
+import { DAEMONS, LEGACY_DAEMONS } from '../../daemon-set';
 import {
   DAEMON_DATAMINE_V1,
   DAEMON_DATAMINE_V2,
@@ -55,6 +56,8 @@ export class BreachProtocolTypesFragment<
   /** Language of current dictionary. */
   private static daemonDictLang: BreachProtocolLanguage = null;
 
+  private static patch: string = null;
+
   private static minAcceptableSimilarity = 0.85;
 
   // Some non latin languages contain latin characters in daemon names.
@@ -87,17 +90,27 @@ export class BreachProtocolTypesFragment<
   }
 
   private setDaemonDict() {
-    if (
-      this.options.recognizer.lang !==
-      BreachProtocolTypesFragment.daemonDictLang
-    ) {
-      const { lang } = this.options.recognizer;
-      const entries = Object.entries(daemonsI18n[lang]).map(
-        ([k, v]: [DaemonId, string]) => [v, k] as const
-      );
+    const hasValidDictionary =
+      this.options.recognizer.lang ===
+      BreachProtocolTypesFragment.daemonDictLang;
+    const hasValidPatch =
+      this.options.patch === BreachProtocolTypesFragment.patch;
+
+    if (!hasValidDictionary || !hasValidPatch) {
+      const { recognizer, patch } = this.options;
+      const { lang } = recognizer;
+      const daemons = patch === '1.x' ? LEGACY_DAEMONS : DAEMONS;
+      const daemonDictEntries = Object.entries(daemonsI18n[lang]) as [
+        DaemonId,
+        string
+      ][];
+      const entries = daemonDictEntries
+        .filter(([daemonId]) => daemons.has(daemonId))
+        .map(([daemonId, translation]) => [translation, daemonId] as const);
 
       BreachProtocolTypesFragment.daemonDict = Object.fromEntries(entries);
       BreachProtocolTypesFragment.daemonDictLang = lang;
+      BreachProtocolTypesFragment.patch = patch;
     }
   }
 
@@ -146,7 +159,10 @@ export class BreachProtocolTypesFragment<
     const keys = Object.keys(BreachProtocolTypesFragment.daemonDict);
 
     return lines.map((t) => {
-      if (BreachProtocolTypesFragment.edgeCases.has(t)) {
+      if (
+        this.options.patch === '1.x' &&
+        BreachProtocolTypesFragment.edgeCases.has(t)
+      ) {
         return BreachProtocolTypesFragment.edgeCases.get(t);
       }
 
diff --git a/src/core/ocr/ocr.ts b/src/core/ocr/ocr.ts
index 8b1ce27..5135d56 100644
--- a/src/core/ocr/ocr.ts
+++ b/src/core/ocr/ocr.ts
@@ -29,6 +29,7 @@ export interface BreachProtocolOCROptions extends FragmentOptions {
   skipTypesFragment?: boolean;
   useFixedBufferSize?: boolean;
   fixedBufferSize?: number;
+  patch: '1.x' | '2.x';
 }
 
 type FragmentCtor = new (
@@ -84,6 +85,7 @@ class BreachProtocolFragmentFactory<TImage> {
       filterRecognizerResults,
       extendedDaemonsAndTypesRecognitionRange,
       extendedBufferSizeRecognitionRange,
+      patch,
     } = options;
 
     return {
@@ -91,6 +93,7 @@ class BreachProtocolFragmentFactory<TImage> {
       extendedBufferSizeRecognitionRange,
       extendedDaemonsAndTypesRecognitionRange,
       filterRecognizerResults,
+      patch,
     };
   }
 }
diff --git a/src/core/solver/breach-protocol.spec.ts b/src/core/solver/breach-protocol.spec.ts
index a913ff6..077cd5d 100644
--- a/src/core/solver/breach-protocol.spec.ts
+++ b/src/core/solver/breach-protocol.spec.ts
@@ -32,7 +32,11 @@ const testData = (data as BreachProtocolRawData[]).concat(
 
 describe('Breach protocol solve', () => {
   const hierarchyProvider = new IndexHierarchyProvider();
-  const options: BreachProtocolOptions = { strategy: 'bfs', hierarchyProvider };
+  const options: BreachProtocolOptions = {
+    strategy: 'bfs',
+    hierarchyProvider,
+    patch: '1.x',
+  };
 
   it('should resolve 3 base cases', () => {
     // prettier-ignore
@@ -70,6 +74,7 @@ describe('Breach protocol solve', () => {
         const game = new BreachProtocol(rawData, {
           strategy,
           hierarchyProvider,
+          patch: '1.x',
         });
         const result = game.solve();
 
diff --git a/src/core/solver/breach-protocol.ts b/src/core/solver/breach-protocol.ts
index 454e40f..8da3b9d 100644
--- a/src/core/solver/breach-protocol.ts
+++ b/src/core/solver/breach-protocol.ts
@@ -55,7 +55,10 @@ export class BreachProtocolResult implements Serializable {
     const { bufferSize, daemons } = this.game.rawData;
 
     // BP will exit automatically when all of the buffer has been used.
-    const willExit = this.path.length === bufferSize;
+    const willExit =
+      this.game.options.patch === '1.x'
+        ? this.path.length === bufferSize
+        : false;
 
     // Get daemons that were not used in resolved sequence.
     // There is no point of finding shortest daemon,
@@ -123,6 +126,7 @@ export interface BreachProtocolOptions
   extends Pick<SequenceFactoryOptions, 'immediate'> {
   strategy: BreachProtocolStrategy;
   hierarchyProvider: HierarchyProvider<BreachProtocolRawData>;
+  patch: '1.x' | '2.x';
 }
 
 export type BreachProtocolStrategy = 'dfs' | 'bfs';
@@ -161,7 +165,7 @@ export class BreachProtocol {
 
   constructor(
     public readonly rawData: BreachProtocolRawData,
-    private readonly options: BreachProtocolOptions
+    public readonly options: BreachProtocolOptions
   ) {}
 
   solveForSequence(sequence: Sequence) {
diff --git a/src/electron/common/common.ts b/src/electron/common/common.ts
index eca5672..81be0f0 100644
--- a/src/electron/common/common.ts
+++ b/src/electron/common/common.ts
@@ -61,7 +61,7 @@ export const KEY_BINDS = [
   'keyBindAnalyze',
 ] as const;
 
-export type BreachProtocolKeyBinds = typeof KEY_BINDS[number];
+export type BreachProtocolKeyBinds = (typeof KEY_BINDS)[number];
 export type KeyBindsConfig = Record<BreachProtocolKeyBinds, string>;
 
 export const COMMANDS = [
@@ -76,7 +76,7 @@ export const COMMANDS = [
   'worker:analyze',
 ] as const;
 
-export type BreachProtocolCommands = typeof COMMANDS[number];
+export type BreachProtocolCommands = (typeof COMMANDS)[number];
 
 export interface AppSettings
   extends RobotSettings,
@@ -86,6 +86,7 @@ export interface AppSettings
     ResolverSettings,
     Required<BreachProtocolOCROptions>,
     Exclude<BreachProtocolOptions, 'hierarchyProvider'> {
+  patch: '1.x' | '2.x';
   historySize: number;
   preserveSourceOnSuccess: boolean;
   checkForUpdates: boolean;
diff --git a/src/electron/common/options.ts b/src/electron/common/options.ts
index 428c0e7..6f98ceb 100644
--- a/src/electron/common/options.ts
+++ b/src/electron/common/options.ts
@@ -8,12 +8,15 @@ import {
   VK_ESCAPE,
 } from '@/common';
 import {
+  DAEMON_ADVANCED_DATAMINE,
+  DAEMON_BASIC_DATAMINE,
   DAEMON_CAMERA_SHUTDOWN,
   DAEMON_DATAMINE_COPY_MALWARE,
   DAEMON_DATAMINE_CRAFTING_SPECS,
   DAEMON_DATAMINE_V1,
   DAEMON_DATAMINE_V2,
   DAEMON_DATAMINE_V3,
+  DAEMON_EXPERT_DATAMINE,
   DAEMON_FRIENDLY_TURRETS,
   DAEMON_GAIN_ACCESS,
   DAEMON_ICEPICK,
@@ -331,6 +334,9 @@ export const options: BreachProtocolOption[] = [
       DAEMON_DATAMINE_V3,
       DAEMON_DATAMINE_V2,
       DAEMON_DATAMINE_V1,
+      DAEMON_EXPERT_DATAMINE,
+      DAEMON_ADVANCED_DATAMINE,
+      DAEMON_BASIC_DATAMINE,
     ],
   },
   {
@@ -339,6 +345,11 @@ export const options: BreachProtocolOption[] = [
       'Determines if sequences should be emitted immediately, or should they be grouped by permutation of daemons. Grouped sequences are sorted by raw path length.',
     defaultValue: false,
   },
+  {
+    id: 'patch',
+    description: 'Installed patch of Cyberpunk 2077.',
+    defaultValue: '2.x',
+  },
 ];
 
 function optionsToObject<T>(cb: (option: BreachProtocolOption) => T) {
diff --git a/src/electron/main/store/store.ts b/src/electron/main/store/store.ts
index c991a26..b0986c8 100644
--- a/src/electron/main/store/store.ts
+++ b/src/electron/main/store/store.ts
@@ -19,6 +19,11 @@ import ElectronStore from 'electron-store';
 import { ensureDirSync, remove, removeSync } from 'fs-extra';
 import { join } from 'path';
 import { appReducer } from './reducer';
+import {
+  DAEMON_ADVANCED_DATAMINE,
+  DAEMON_BASIC_DATAMINE,
+  DAEMON_EXPERT_DATAMINE,
+} from '@/core';
 
 type Middleware = (action: Action) => void;
 
@@ -82,6 +87,26 @@ export class Store {
           });
         }
       },
+      '>=2.9.0': (store) => {
+        const daemons = store.get('daemonPriority');
+        const newDaemons = [];
+
+        if (!daemons.includes(DAEMON_EXPERT_DATAMINE)) {
+          newDaemons.push(DAEMON_EXPERT_DATAMINE);
+        }
+
+        if (!daemons.includes(DAEMON_ADVANCED_DATAMINE)) {
+          newDaemons.push(DAEMON_ADVANCED_DATAMINE);
+        }
+
+        if (!daemons.includes(DAEMON_BASIC_DATAMINE)) {
+          newDaemons.push(DAEMON_BASIC_DATAMINE);
+        }
+
+        if (newDaemons.length > 0) {
+          store.set('daemonPriority', [...daemons, ...newDaemons]);
+        }
+      },
     },
   });
 
diff --git a/src/electron/renderer/common.ts b/src/electron/renderer/common.ts
index 1b95cb5..d5cc9ae 100644
--- a/src/electron/renderer/common.ts
+++ b/src/electron/renderer/common.ts
@@ -90,6 +90,14 @@ export function getDisplayName(display: ScreenshotDisplayOutput) {
   return `${display.name} (${display.width}x${display.height})`;
 }
 
+export function getPatchName(patch: '1.x' | '2.x') {
+  if (patch === '1.x') {
+    return '1.63';
+  }
+
+  return '2.0';
+}
+
 export function createRootElement(id: string) {
   const root = document.createElement('div');
   root.id = id;
diff --git a/src/electron/renderer/components/DaemonPriority.tsx b/src/electron/renderer/components/DaemonPriority.tsx
index ff2daa7..790c775 100644
--- a/src/electron/renderer/components/DaemonPriority.tsx
+++ b/src/electron/renderer/components/DaemonPriority.tsx
@@ -15,6 +15,9 @@ import {
   DAEMON_TURRET_SHUTDOWN,
   DAEMON_WEAPONS_JAMMER,
   eng,
+  DAEMON_BASIC_DATAMINE,
+  DAEMON_ADVANCED_DATAMINE,
+  DAEMON_EXPERT_DATAMINE,
 } from '@/core';
 import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
 import { flushSync } from 'react-dom';
@@ -22,7 +25,9 @@ import styled from 'styled-components';
 import { FlatButton } from './Buttons';
 import { Dialog } from './Dialog';
 import { Col, Row, Spacer } from './Flex';
-import { useField } from './Form';
+import { useField, useForm } from './Form';
+import { AppSettings } from '@/electron/common';
+import { DAEMONS, LEGACY_DAEMONS } from '@/core/daemon-set';
 
 const Description = styled.p`
   color: var(--primary);
@@ -34,10 +39,22 @@ const Description = styled.p`
 
 const Daemon = styled(Row).attrs({
   role: 'button',
-})<{ selected: boolean }>`
-  color: ${(p) => (p.selected ? 'var(--background)' : 'var(--accent)')};
-  background: ${(p) => (p.selected ? 'var(--accent)' : 'var(--background)')};
-  border: 1px solid ${(p) => (p.selected ? 'var(--accent)' : 'var(--primary)')};
+})<{ selected: boolean; disabled: boolean }>`
+  color: ${(p) =>
+    p.disabled ? 'gray' : p.selected ? 'var(--background)' : 'var(--accent)'};
+  background: ${(p) =>
+    p.disabled
+      ? 'darkgray'
+      : p.selected
+      ? 'var(--accent)'
+      : 'var(--background)'};
+  border: 1px solid
+    ${(p) =>
+      p.disabled
+        ? 'darkgray'
+        : p.selected
+        ? 'var(--accent)'
+        : 'var(--primary)'};
   display: flex;
   align-items: center;
   justify-content: center;
@@ -65,6 +82,9 @@ const commonDaemons: ReadonlySet<DaemonId> = new Set([
   DAEMON_DATAMINE_V2,
   DAEMON_DATAMINE_V3,
   DAEMON_ICEPICK,
+  DAEMON_BASIC_DATAMINE,
+  DAEMON_ADVANCED_DATAMINE,
+  DAEMON_EXPERT_DATAMINE,
 ]);
 
 const perkDaemons: ReadonlySet<DaemonId> = new Set([
@@ -119,10 +139,15 @@ interface DaemonPriorityProps {
 }
 
 export const DaemonPriority = ({ isOpen, onClose }: DaemonPriorityProps) => {
+  const { values } = useForm<AppSettings>();
   const [selected, setSelected] = useState<string>(null);
   const { value, setValue } = useField<DaemonId[]>();
   const didMount = useRef(false);
   const [dirty, setDirty] = useState(false);
+  const daemonSet = useMemo(
+    () => (values.patch === '1.x' ? LEGACY_DAEMONS : DAEMONS),
+    [values.patch]
+  );
   const defaultDaemons = useMemo(() => {
     return value.map((id) => {
       const type = getDaemonType(id);
@@ -239,6 +264,7 @@ export const DaemonPriority = ({ isOpen, onClose }: DaemonPriorityProps) => {
             {daemons.map(({ id, type }) => (
               <Daemon
                 key={id}
+                disabled={!daemonSet.has(id)}
                 selected={selected === id}
                 onClick={() =>
                   setSelected((selected) => (selected === id ? null : id))
diff --git a/src/electron/renderer/components/GeneralSettings.tsx b/src/electron/renderer/components/GeneralSettings.tsx
index 6433d92..45b4c92 100644
--- a/src/electron/renderer/components/GeneralSettings.tsx
+++ b/src/electron/renderer/components/GeneralSettings.tsx
@@ -1,9 +1,16 @@
 import { RemoveLastNHistoryEntriesAction } from '@/electron/common';
-import { nativeDialog } from '../common';
+import { getPatchName, nativeDialog } from '../common';
 import { Field, Label, OnBeforeValueChange } from './Form';
 import { RangeSlider } from './RangeSlider';
 import { Section } from './Section';
 import { Switch } from './Switch';
+import { Select, SelectOption } from './Select';
+
+const patches = ['1.x', '2.x'] as const;
+const patchOptions: SelectOption<string>[] = patches.map((value) => ({
+  value,
+  name: getPatchName(value),
+}));
 
 export const GeneralSettings = ({ historySize }: { historySize: number }) => {
   const onBeforeHistorySizeChange: OnBeforeValueChange<number> = async (
@@ -30,6 +37,10 @@ export const GeneralSettings = ({ historySize }: { historySize: number }) => {
 
   return (
     <Section title="General">
+      <Field name="patch">
+        <Label>Cyberpunk patch</Label>
+        <Select options={patchOptions}></Select>
+      </Field>
       <Field name="minimizeToTray">
         <Label>Minimize to tray</Label>
         <Switch />
diff --git a/src/electron/renderer/components/StatusBar.tsx b/src/electron/renderer/components/StatusBar.tsx
index fab6ef1..90dac5c 100644
--- a/src/electron/renderer/components/StatusBar.tsx
+++ b/src/electron/renderer/components/StatusBar.tsx
@@ -2,7 +2,7 @@ import { ActionTypes, UpdateStatus, WorkerStatus } from '@/electron/common';
 import { ProgressInfo } from 'electron-updater';
 import { useContext, useEffect, useState } from 'react';
 import styled from 'styled-components';
-import { getDisplayName, useIpcEvent } from '../common';
+import { getDisplayName, getPatchName, useIpcEvent } from '../common';
 import { RouterExtContext } from '../router-ext';
 import { StateContext } from '../state';
 import { Spacer } from './Flex';
@@ -139,7 +139,7 @@ export const StatusBar = () => {
     displays,
     status,
     updateStatus,
-    settings: { activeDisplayId },
+    settings: { activeDisplayId, patch },
   } = useContext(StateContext);
   const { navigateToSetting } = useContext(RouterExtContext);
   const show = useSettingsChangeListener();
@@ -152,6 +152,9 @@ export const StatusBar = () => {
   return (
     <StatusBarWrapper>
       <StatusBarItem>{VERSION}</StatusBarItem>
+      <InteractiveStatusBarItem onClick={() => navigateToSetting('patch')}>
+        patch: {getPatchName(patch)}
+      </InteractiveStatusBarItem>
       <InteractiveStatusBarItem
         onClick={() => navigateToSetting('activeDisplayId')}
       >
diff --git a/src/electron/renderer/components/TypesFragmentStatus.tsx b/src/electron/renderer/components/TypesFragmentStatus.tsx
index fbbf2cc..8de1ff4 100644
--- a/src/electron/renderer/components/TypesFragmentStatus.tsx
+++ b/src/electron/renderer/components/TypesFragmentStatus.tsx
@@ -14,7 +14,8 @@ export const TypesFragmentStatus = ({ types }: TypesFragmentStatusProps) => {
       body={
         <Warning.Body>
           Select correct{' '}
-          <Warning.Link setting="gameLang">game language</Warning.Link>, change{' '}
+          <Warning.Link setting="gameLang">game language</Warning.Link>,{' '}
+          <Warning.Link setting="patch">patch</Warning.Link>, change{' '}
           <Warning.Link setting="thresholdTypesAuto">threshold</Warning.Link> or{' '}
           <Warning.Link setting="skipTypesFragment">
             disable this feature
diff --git a/src/electron/worker/autosolver.ts b/src/electron/worker/autosolver.ts
index dbcb104..c0b6cb4 100644
--- a/src/electron/worker/autosolver.ts
+++ b/src/electron/worker/autosolver.ts
@@ -102,6 +102,7 @@ export class BreachProtocolAutosolver {
     this.game = new BreachProtocol(rawData, {
       strategy: this.settings.strategy,
       hierarchyProvider: this.hierarchyProvider,
+      patch: this.settings.patch,
     });
 
     this.progress.add(BreachProtocolSolveProgress.FragmentsValid);
diff --git a/src/electron/worker/worker.ts b/src/electron/worker/worker.ts
index 9284b25..0a1b94e 100644
--- a/src/electron/worker/worker.ts
+++ b/src/electron/worker/worker.ts
@@ -377,6 +377,7 @@ export class BreachProtocolWorker {
       gameLang,
       extendedBufferSizeRecognitionRange,
       extendedDaemonsAndTypesRecognitionRange,
+      patch,
     } = this.settings;
     const container = await SharpImageContainer.create(instance, {
       downscaleSource,
@@ -387,6 +388,7 @@ export class BreachProtocolWorker {
       filterRecognizerResults,
       extendedBufferSizeRecognitionRange,
       extendedDaemonsAndTypesRecognitionRange,
+      patch,
     };
 
     this.fragments = {


Test patch:
diff --git a/src/core/ocr/ocr.test.ts b/src/core/ocr/ocr.test.ts
index 91a99ad..dfd13bc 100644
--- a/src/core/ocr/ocr.test.ts
+++ b/src/core/ocr/ocr.test.ts
@@ -136,13 +136,16 @@ describe('raw data validation', () => {
   it('should pass it if data is valid', () => {
     const gridFragment = new BreachProtocolGridFragment(container, {
       recognizer,
+      patch: '1.x',
     });
     const daemonsFragment = new BreachProtocolDaemonsFragment(container, {
       recognizer,
       extendedDaemonsAndTypesRecognitionRange: false,
+      patch: '1.x',
     });
     const bufferSizeFragment = new BreachProtocolBufferSizeFragment(container, {
       extendedBufferSizeRecognitionRange: false,
+      patch: '1.x',
     });
 
     expect(gridFragment.getStatus(grid)).toBe(valid);
@@ -151,7 +154,10 @@ describe('raw data validation', () => {
   });
 
   it('should throw an error if grid is invalid', () => {
-    const fragment = new BreachProtocolGridFragment(container, { recognizer });
+    const fragment = new BreachProtocolGridFragment(container, {
+      recognizer,
+      patch: '1.x',
+    });
     const invalidGrids = [
       grid.map((s, i) => (i === 5 ? '57' : s)),
       grid.map((s, i) => (i === 9 ? 'test' : s)),
@@ -169,6 +175,7 @@ describe('raw data validation', () => {
     const fragment = new BreachProtocolDaemonsFragment(container, {
       recognizer,
       extendedDaemonsAndTypesRecognitionRange: false,
+      patch: '1.x',
     });
     const invalidDaemons = [
       daemons.map(() => ['B7']),
@@ -185,6 +192,7 @@ describe('raw data validation', () => {
   it('should throw an error if buffer size is invalid', () => {
     const fragment = new BreachProtocolBufferSizeFragment(container, {
       extendedBufferSizeRecognitionRange: false,
+      patch: '1.x',
     });
     const invalidBufferSizes = [NaN, 3, 10, 2 * Math.PI] as BufferSize[];
 
@@ -246,11 +254,13 @@ async function recognizeRegistryEntry(
     settings?.extendedBufferSizeRecognitionRange ?? false;
   const trimStrategy = new BreachProtocolBufferSizeTrimFragment(container, {
     extendedBufferSizeRecognitionRange,
+    patch: '1.x',
   });
   const recognizer = new WasmBreachProtocolRecognizer(null);
 
   return Promise.all([
     breachProtocolOCR(container, recognizer, {
+      patch: '1.x',
       thresholdGridAuto: true,
       thresholdTypesAuto: true,
       thresholdDaemonsAuto: true,