mirror of
https://github.com/sp-tarkov/modules.git
synced 2025-02-12 17:10:44 -05:00
Add repo
This commit is contained in:
commit
b3ce0ec36f
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
###############################################################################
|
||||||
|
# Set default behavior to automatically normalize line endings.
|
||||||
|
###############################################################################
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set default behavior for command prompt diff.
|
||||||
|
#
|
||||||
|
# This is need for earlier builds of msysgit that does not have it on by
|
||||||
|
# default for csharp files.
|
||||||
|
# Note: This is only used by command line
|
||||||
|
###############################################################################
|
||||||
|
#*.cs diff=csharp
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set the merge driver for project and solution files
|
||||||
|
#
|
||||||
|
# Merging from the command prompt will add diff markers to the files if there
|
||||||
|
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||||
|
# the diff markers are never inserted). Diff markers may cause the following
|
||||||
|
# file extensions to fail to load in VS. An alternative would be to treat
|
||||||
|
# these files as binary and thus will always conflict and require user
|
||||||
|
# intervention with every merge. To do so, just uncomment the entries below
|
||||||
|
###############################################################################
|
||||||
|
#*.sln merge=binary
|
||||||
|
#*.csproj merge=binary
|
||||||
|
#*.vbproj merge=binary
|
||||||
|
#*.vcxproj merge=binary
|
||||||
|
#*.vcproj merge=binary
|
||||||
|
#*.dbproj merge=binary
|
||||||
|
#*.fsproj merge=binary
|
||||||
|
#*.lsproj merge=binary
|
||||||
|
#*.wixproj merge=binary
|
||||||
|
#*.modelproj merge=binary
|
||||||
|
#*.sqlproj merge=binary
|
||||||
|
#*.wwaproj merge=binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# behavior for image files
|
||||||
|
#
|
||||||
|
# image files are treated as binary by default.
|
||||||
|
###############################################################################
|
||||||
|
#*.jpg binary
|
||||||
|
#*.png binary
|
||||||
|
#*.gif binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# diff behavior for common document formats
|
||||||
|
#
|
||||||
|
# Convert binary document formats to text before diffing them. This feature
|
||||||
|
# is only available from the command line. Turn it on by uncommenting the
|
||||||
|
# entries below.
|
||||||
|
###############################################################################
|
||||||
|
#*.doc diff=astextplain
|
||||||
|
#*.DOC diff=astextplain
|
||||||
|
#*.docx diff=astextplain
|
||||||
|
#*.DOCX diff=astextplain
|
||||||
|
#*.dot diff=astextplain
|
||||||
|
#*.DOT diff=astextplain
|
||||||
|
#*.pdf diff=astextplain
|
||||||
|
#*.PDF diff=astextplain
|
||||||
|
#*.rtf diff=astextplain
|
||||||
|
#*.RTF diff=astextplain
|
355
.gitignore
vendored
Normal file
355
.gitignore
vendored
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# Aki
|
||||||
|
**/Build/
|
||||||
|
**/Shared/Managed/*
|
||||||
|
**/tools/
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Bb]in/[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
32
LICENSE.md
Normal file
32
LICENSE.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# NCSA Open Source License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Merijn Hendriks. All rights reserved.
|
||||||
|
|
||||||
|
Developed by: Merijn Hendriks
|
||||||
|
SPT-AKI
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
with the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimers.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimers in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the names of Merijn Hendriks, SPT-AKI, nor the names of
|
||||||
|
its contributors may be used to endorse or promote products derived from
|
||||||
|
this Software without specific prior written permission.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||||
|
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS WITH THE SOFTWARE.
|
31
README.md
Normal file
31
README.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Modules
|
||||||
|
|
||||||
|
BepInEx plugins to alter Escape From Tarkov's behaviour
|
||||||
|
|
||||||
|
**Project** | **Function**
|
||||||
|
------------------ | --------------------------------------------
|
||||||
|
Aki.Build | Build script
|
||||||
|
Aki.Bundles | External bundle loader
|
||||||
|
Aki.Common | Common utilities used across projects
|
||||||
|
Aki.Core | Required patches to start the game
|
||||||
|
Aki.Custom | SPT-AKI enhancements to EFT
|
||||||
|
Aki.Debugging | Debug utilities (disabled in release builds)
|
||||||
|
Aki.Reflection | Reflection utilities used across the project
|
||||||
|
Aki.SinglePlayer | Simulating online game while offline
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Escape From Tarkov 22032
|
||||||
|
- BepInEx 5.4.19
|
||||||
|
- Visual Studio Code
|
||||||
|
- .NET 6 SDK
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Copy-paste Live EFT's `EscapeFromTarkov_Data/Managed/` folder to into Modules' `Project/Shared/` folder
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
1. File > Open Workspace > Modules.code-workspace
|
||||||
|
2. Terminal > Run Build Task...
|
||||||
|
3. Copy-paste content inside `Build` into `%gamedir%`, overwrite when prompted.
|
128
docs/packetsniffer.md
Normal file
128
docs/packetsniffer.md
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# Packet Sniffer
|
||||||
|
|
||||||
|
References are based on version 0.12.12.15.17566
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- de4dot
|
||||||
|
- dnspy
|
||||||
|
|
||||||
|
## Deobfuscation
|
||||||
|
|
||||||
|
```cs
|
||||||
|
// Token: 0x0600D716 RID: 55062 RVA: 0x00127E88 File Offset: 0x00126088
|
||||||
|
Class2082.smethod_0()
|
||||||
|
{
|
||||||
|
return (string)((Hashtable)AppDomain.CurrentDomain.GetData(Class2082.string_0))[int_0];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
de4dot-x64.exe Assembly-CSharp.dll
|
||||||
|
de4dot-x64.exe --un-name "!^<>[a-z0-9]$&!^<>[a-z0-9]__.*$&![A-Z][A-Z]\$<>.*$&^[a-zA-Z_<{$][a-zA-Z_0-9<>{}$.`-]*$" "Assembly-CSharp-cleaned.dll" --strtyp delegate --strtok 0x0600D716
|
||||||
|
pause
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fix ResolutionScope error
|
||||||
|
|
||||||
|
1. DnSpy > File > Open... > `Assembly-CSharp-cleaned-cleaned.dll`
|
||||||
|
2. DnSpy > File > Save module.. > OK
|
||||||
|
|
||||||
|
## Modifications
|
||||||
|
|
||||||
|
### Assembly-CSharp.dll
|
||||||
|
|
||||||
|
#### Save requests
|
||||||
|
|
||||||
|
```cs
|
||||||
|
// Token: 0x06001CF6 RID: 7414 RVA: 0x0019CAC8 File Offset: 0x0019ACC8
|
||||||
|
[postfix]
|
||||||
|
Class182.method_2()
|
||||||
|
{
|
||||||
|
var uri = new Uri(url);
|
||||||
|
var path = (System.IO.Directory.GetCurrentDirectory() + "\\HTTP_DATA\\").Replace("\\\\", "\\");
|
||||||
|
var file = uri.LocalPath.Replace('/', '.').Remove(0, 1);
|
||||||
|
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||||
|
|
||||||
|
if (System.IO.Directory.CreateDirectory(path).Exists && obj != null)
|
||||||
|
{
|
||||||
|
System.IO.File.WriteAllText($@"{path}req.{file}_{time}.json", text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Save responses
|
||||||
|
|
||||||
|
```cs
|
||||||
|
// Token: 0x06001D01 RID: 7425 RVA: 0x0019D200 File Offset: 0x0019B400
|
||||||
|
[postfix]
|
||||||
|
Class182.method_8()
|
||||||
|
{
|
||||||
|
// add this at the end, before "return text3;"
|
||||||
|
// in case you turn this into a harmony patch, text3 = __result
|
||||||
|
var uri = new Uri(url);
|
||||||
|
var path = (System.IO.Directory.GetCurrentDirectory() + "\\HTTP_DATA\\").Replace("\\\\", "\\");
|
||||||
|
var file = uri.LocalPath.Replace('/', '.').Remove(0, 1);
|
||||||
|
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||||
|
|
||||||
|
if (System.IO.Directory.CreateDirectory(path).Exists)
|
||||||
|
{
|
||||||
|
System.IO.File.WriteAllText($@"{path}resp.{file}_{time}.json", text3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Disable SSL certification
|
||||||
|
|
||||||
|
```cs
|
||||||
|
// Token: 0x0600509D RID: 20637 RVA: 0x0027D244 File Offset: 0x0027B444
|
||||||
|
[prefix]
|
||||||
|
Class537.ValidateCertificate()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```cs
|
||||||
|
// Token: 0x0600509E RID: 20638 RVA: 0x0027D2B4 File Offset: 0x0027B4B4
|
||||||
|
[prefix]
|
||||||
|
Class537.ValidateCertificate()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Battleye
|
||||||
|
|
||||||
|
```cs
|
||||||
|
// Token: 0x06006B7A RID: 27514 RVA: 0x002D55B8 File Offset: 0x002D37B8
|
||||||
|
[prefix]
|
||||||
|
Class815.RunValidation()
|
||||||
|
{
|
||||||
|
this.Succeed = true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### FilesChecker.dll
|
||||||
|
|
||||||
|
#### Consistency multi
|
||||||
|
|
||||||
|
```cs
|
||||||
|
// Token: 0x06000054 RID: 84 RVA: 0x00002A38 File Offset: 0x00000C38
|
||||||
|
[prefix]
|
||||||
|
ConsistencyController.EnsureConsistency()
|
||||||
|
{
|
||||||
|
return Task.FromResult<ICheckResult>(ConsistencyController.CheckResult.Succeed(new TimeSpan()));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Consistency single
|
||||||
|
|
||||||
|
```cs
|
||||||
|
// Token: 0x06000053 RID: 83 RVA: 0x000028D4 File Offset: 0x00000AD4
|
||||||
|
[prefix]
|
||||||
|
ConsistencyController.EnsureConsistencySingle()
|
||||||
|
{
|
||||||
|
return Task.FromResult<ICheckResult>(ConsistencyController.CheckResult.Succeed(new TimeSpan()));
|
||||||
|
}
|
||||||
|
```
|
33
project/Aki.Build/Aki.Build.csproj
Normal file
33
project/Aki.Build/Aki.Build.csproj
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net472</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Company>Aki</Company>
|
||||||
|
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="Build.ps1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||||
|
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.Core\Aki.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.SinglePlayer\Aki.SinglePlayer.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
<Exec Command="dotnet cake "../build.cake" --vsbuilt=true" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
24
project/Aki.Common/Aki.Common.csproj
Normal file
24
project/Aki.Common/Aki.Common.csproj
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net472</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Company>Aki</Company>
|
||||||
|
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="bsg.componentace.compression.libs.zlib" HintPath="..\Shared\Managed\bsg.componentace.compression.libs.zlib.dll" Private="False" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="BepInEx.Core" Version="5.4.21" />
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||||
|
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
83
project/Aki.Common/Http/Request.cs
Normal file
83
project/Aki.Common/Http/Request.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using Aki.Common.Utils;
|
||||||
|
|
||||||
|
namespace Aki.Common.Http
|
||||||
|
{
|
||||||
|
public class Request
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Send a request to remote endpoint and optionally receive a response body.
|
||||||
|
/// Deflate is the accepted compression format.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] Send(string url, string method, byte[] data = null, bool compress = true, string mime = null, Dictionary<string, string> headers = null)
|
||||||
|
{
|
||||||
|
if (!WebConstants.IsValidMethod(method))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("request method is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri uri = new Uri(url);
|
||||||
|
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
|
||||||
|
|
||||||
|
if (uri.Scheme == "https")
|
||||||
|
{
|
||||||
|
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||||||
|
request.ServerCertificateValidationCallback = delegate { return true; };
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Timeout = 15000;
|
||||||
|
request.Method = method;
|
||||||
|
request.Headers.Add("Accept-Encoding", "deflate");
|
||||||
|
|
||||||
|
if (headers != null)
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<string, string> item in headers)
|
||||||
|
{
|
||||||
|
request.Headers.Add(item.Key, item.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method != WebConstants.Get && method != WebConstants.Head && data != null)
|
||||||
|
{
|
||||||
|
byte[] body = (compress) ? Zlib.Compress(data, ZlibCompression.Maximum) : data;
|
||||||
|
|
||||||
|
request.ContentType = WebConstants.IsValidMime(mime) ? mime : "application/octet-stream";
|
||||||
|
request.ContentLength = body.Length;
|
||||||
|
|
||||||
|
if (compress)
|
||||||
|
{
|
||||||
|
request.Headers.Add("Content-Encoding", "deflate");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (Stream stream = request.GetRequestStream())
|
||||||
|
{
|
||||||
|
stream.Write(body, 0, body.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (WebResponse response = request.GetResponse())
|
||||||
|
{
|
||||||
|
using (MemoryStream ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
response.GetResponseStream().CopyTo(ms);
|
||||||
|
byte[] body = ms.ToArray();
|
||||||
|
|
||||||
|
if (body.Length == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Zlib.IsCompressed(body))
|
||||||
|
{
|
||||||
|
return Zlib.Decompress(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
project/Aki.Common/Http/RequestHandler.cs
Normal file
111
project/Aki.Common/Http/RequestHandler.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Aki.Common.Utils;
|
||||||
|
using BepInEx.Logging;
|
||||||
|
|
||||||
|
namespace Aki.Common.Http
|
||||||
|
{
|
||||||
|
public static class RequestHandler
|
||||||
|
{
|
||||||
|
private static string _host;
|
||||||
|
private static string _session;
|
||||||
|
private static Request _request;
|
||||||
|
private static Dictionary<string, string> _headers;
|
||||||
|
private static ManualLogSource _logger;
|
||||||
|
|
||||||
|
static RequestHandler()
|
||||||
|
{
|
||||||
|
_logger = Logger.CreateLogSource(nameof(RequestHandler));
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Initialize()
|
||||||
|
{
|
||||||
|
_request = new Request();
|
||||||
|
|
||||||
|
string[] args = Environment.GetCommandLineArgs();
|
||||||
|
|
||||||
|
foreach (string arg in args)
|
||||||
|
{
|
||||||
|
if (arg.Contains("BackendUrl"))
|
||||||
|
{
|
||||||
|
string json = arg.Replace("-config=", string.Empty);
|
||||||
|
_host = Json.Deserialize<ServerConfig>(json).BackendUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.Contains("-token="))
|
||||||
|
{
|
||||||
|
_session = arg.Replace("-token=", string.Empty);
|
||||||
|
_headers = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "Cookie", $"PHPSESSID={_session}" },
|
||||||
|
{ "SessionId", _session }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateData(byte[] data)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Request failed, body is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInfo($"Request was successful");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateJson(string json)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
|
{
|
||||||
|
_logger.LogError($"Request failed, body is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInfo($"Request was successful");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] GetData(string path, bool hasHost = false)
|
||||||
|
{
|
||||||
|
string url = (hasHost) ? path : _host + path;
|
||||||
|
|
||||||
|
_logger.LogInfo($"Request GET data: {_session}:{url}");
|
||||||
|
byte[] result = _request.Send(url, "GET", null, headers: _headers);
|
||||||
|
|
||||||
|
ValidateData(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetJson(string path, bool hasHost = false)
|
||||||
|
{
|
||||||
|
string url = (hasHost) ? path : _host + path;
|
||||||
|
|
||||||
|
_logger.LogInfo($"Request GET json: {_session}:{url}");
|
||||||
|
byte[] data = _request.Send(url, "GET", headers: _headers);
|
||||||
|
string result = Encoding.UTF8.GetString(data);
|
||||||
|
|
||||||
|
ValidateJson(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string PostJson(string path, string json, bool hasHost = false)
|
||||||
|
{
|
||||||
|
string url = (hasHost) ? path : _host + path;
|
||||||
|
|
||||||
|
_logger.LogInfo($"Request POST json: {_session}:{url}");
|
||||||
|
byte[] data = _request.Send(url, "POST", Encoding.UTF8.GetBytes(json), true, "application/json", _headers);
|
||||||
|
string result = Encoding.UTF8.GetString(data);
|
||||||
|
|
||||||
|
ValidateJson(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PutJson(string path, string json, bool hasHost = false)
|
||||||
|
{
|
||||||
|
string url = (hasHost) ? path : _host + path;
|
||||||
|
_logger.LogInfo($"Request PUT json: {_session}:{url}");
|
||||||
|
_request.Send(url, "PUT", Encoding.UTF8.GetBytes(json), true, "application/json", _headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
project/Aki.Common/Http/ServerConfig.cs
Normal file
14
project/Aki.Common/Http/ServerConfig.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace Aki.Common.Http
|
||||||
|
{
|
||||||
|
public class ServerConfig
|
||||||
|
{
|
||||||
|
public string BackendUrl { get; }
|
||||||
|
public string Version { get; }
|
||||||
|
|
||||||
|
public ServerConfig(string backendUrl, string version)
|
||||||
|
{
|
||||||
|
BackendUrl = backendUrl;
|
||||||
|
Version = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
94
project/Aki.Common/Http/WebConstants.cs
Normal file
94
project/Aki.Common/Http/WebConstants.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Aki.Common.Http
|
||||||
|
{
|
||||||
|
public static class WebConstants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// HTML GET method.
|
||||||
|
/// </summary>
|
||||||
|
public const string Get = "GET";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTML HEAD method.
|
||||||
|
/// </summary>
|
||||||
|
public const string Head = "HEAD";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTML POST method.
|
||||||
|
/// </summary>
|
||||||
|
public const string Post = "POST";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTML PUT method.
|
||||||
|
/// </summary>
|
||||||
|
public const string Put = "PUT";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTML DELETE method.
|
||||||
|
/// </summary>
|
||||||
|
public const string Delete = "DELETE";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTML CONNECT method.
|
||||||
|
/// </summary>
|
||||||
|
public const string Connect = "CONNECT";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTML OPTIONS method.
|
||||||
|
/// </summary>
|
||||||
|
public const string Options = "OPTIONS";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTML TRACE method.
|
||||||
|
/// </summary>
|
||||||
|
public const string Trace = "TRACE";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTML MIME types.
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, string> Mime { get; private set; }
|
||||||
|
|
||||||
|
static WebConstants()
|
||||||
|
{
|
||||||
|
Mime = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ ".bin", "application/octet-stream" },
|
||||||
|
{ ".txt", "text/plain" },
|
||||||
|
{ ".htm", "text/html" },
|
||||||
|
{ ".html", "text/html" },
|
||||||
|
{ ".css", "text/css" },
|
||||||
|
{ ".js", "text/javascript" },
|
||||||
|
{ ".jpeg", "image/jpeg" },
|
||||||
|
{ ".jpg", "image/jpeg" },
|
||||||
|
{ ".png", "image/png" },
|
||||||
|
{ ".ico", "image/vnd.microsoft.icon" },
|
||||||
|
{ ".json", "application/json" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is HTML method valid?
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsValidMethod(string method)
|
||||||
|
{
|
||||||
|
return method == Get
|
||||||
|
|| method == Head
|
||||||
|
|| method == Post
|
||||||
|
|| method == Put
|
||||||
|
|| method == Delete
|
||||||
|
|| method == Connect
|
||||||
|
|| method == Options
|
||||||
|
|| method == Trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is MIME type valid?
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsValidMime(string mime)
|
||||||
|
{
|
||||||
|
return Mime.Any(x => x.Value == mime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
project/Aki.Common/Utils/Json.cs
Normal file
17
project/Aki.Common/Utils/Json.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Aki.Common.Utils
|
||||||
|
{
|
||||||
|
public static class Json
|
||||||
|
{
|
||||||
|
public static string Serialize<T>(T data)
|
||||||
|
{
|
||||||
|
return JsonConvert.SerializeObject(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T Deserialize<T>(string json)
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<T>(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
213
project/Aki.Common/Utils/VFS.cs
Normal file
213
project/Aki.Common/Utils/VFS.cs
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Aki.Common.Utils
|
||||||
|
{
|
||||||
|
public static class VFS
|
||||||
|
{
|
||||||
|
public static string Cwd { get; private set; }
|
||||||
|
|
||||||
|
static VFS()
|
||||||
|
{
|
||||||
|
Cwd = Environment.CurrentDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Combine two filepaths.
|
||||||
|
/// </summary>
|
||||||
|
public static string Combine(string path1, string path2)
|
||||||
|
{
|
||||||
|
return Path.Combine(path1, path2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Combines the filepath with the current working directory.
|
||||||
|
/// </summary>
|
||||||
|
public static string FromCwd(this string filepath)
|
||||||
|
{
|
||||||
|
return Combine(Cwd, filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get directory path of a filepath.
|
||||||
|
/// </summary>
|
||||||
|
public static string GetDirectory(this string filepath)
|
||||||
|
{
|
||||||
|
string value = Path.GetDirectoryName(filepath);
|
||||||
|
return (!string.IsNullOrWhiteSpace(value)) ? value : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get file of a filepath
|
||||||
|
/// </summary>
|
||||||
|
public static string GetFile(this string filepath)
|
||||||
|
{
|
||||||
|
string value = Path.GetFileName(filepath);
|
||||||
|
return (!string.IsNullOrWhiteSpace(value)) ? value : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get file name of a filepath
|
||||||
|
/// </summary>
|
||||||
|
public static string GetFileName(this string filepath)
|
||||||
|
{
|
||||||
|
string value = Path.GetFileNameWithoutExtension(filepath);
|
||||||
|
return (!string.IsNullOrWhiteSpace(value)) ? value : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get file extension of a filepath.
|
||||||
|
/// </summary>
|
||||||
|
public static string GetFileExtension(this string filepath)
|
||||||
|
{
|
||||||
|
string value = Path.GetExtension(filepath);
|
||||||
|
return (!string.IsNullOrWhiteSpace(value)) ? value : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Move file from one place to another
|
||||||
|
/// </summary>
|
||||||
|
public static void MoveFile(string a, string b)
|
||||||
|
{
|
||||||
|
new FileInfo(a).MoveTo(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Does the filepath exist?
|
||||||
|
/// </summary>
|
||||||
|
public static bool Exists(string filepath)
|
||||||
|
{
|
||||||
|
return Directory.Exists(filepath) || File.Exists(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create directory (recursive).
|
||||||
|
/// </summary>
|
||||||
|
public static void CreateDirectory(string filepath)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get file content as bytes.
|
||||||
|
/// </summary>
|
||||||
|
public static byte[] ReadFile(string filepath)
|
||||||
|
{
|
||||||
|
return File.ReadAllBytes(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get file content as string.
|
||||||
|
/// </summary>
|
||||||
|
public static string ReadTextFile(string filepath)
|
||||||
|
{
|
||||||
|
return File.ReadAllText(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write data to file.
|
||||||
|
/// </summary>
|
||||||
|
public static void WriteFile(string filepath, byte[] data)
|
||||||
|
{
|
||||||
|
if (!Exists(filepath))
|
||||||
|
{
|
||||||
|
CreateDirectory(filepath.GetDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllBytes(filepath, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write string to file.
|
||||||
|
/// </summary>
|
||||||
|
public static void WriteTextFile(string filepath, string data, bool append = false)
|
||||||
|
{
|
||||||
|
if (!Exists(filepath))
|
||||||
|
{
|
||||||
|
CreateDirectory(filepath.GetDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (append)
|
||||||
|
{
|
||||||
|
File.AppendAllText(filepath, data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
File.WriteAllText(filepath, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get directories in directory by full path.
|
||||||
|
/// </summary>
|
||||||
|
public static string[] GetDirectories(string filepath)
|
||||||
|
{
|
||||||
|
DirectoryInfo di = new DirectoryInfo(filepath);
|
||||||
|
List<string> paths = new List<string>();
|
||||||
|
|
||||||
|
foreach (DirectoryInfo directory in di.GetDirectories())
|
||||||
|
{
|
||||||
|
paths.Add(directory.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get files in directory by full path.
|
||||||
|
/// </summary>
|
||||||
|
public static string[] GetFiles(string filepath)
|
||||||
|
{
|
||||||
|
DirectoryInfo di = new DirectoryInfo(filepath);
|
||||||
|
List<string> paths = new List<string>();
|
||||||
|
|
||||||
|
foreach (FileInfo file in di.GetFiles())
|
||||||
|
{
|
||||||
|
paths.Add(file.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete directory.
|
||||||
|
/// </summary>
|
||||||
|
public static void DeleteDirectory(string filepath)
|
||||||
|
{
|
||||||
|
DirectoryInfo di = new DirectoryInfo(filepath);
|
||||||
|
|
||||||
|
foreach (FileInfo file in di.GetFiles())
|
||||||
|
{
|
||||||
|
file.IsReadOnly = false;
|
||||||
|
file.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (DirectoryInfo directory in di.GetDirectories())
|
||||||
|
{
|
||||||
|
DeleteDirectory(directory.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
di.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete file.
|
||||||
|
/// </summary>
|
||||||
|
public static void DeleteFile(string filepath)
|
||||||
|
{
|
||||||
|
FileInfo file = new FileInfo(filepath);
|
||||||
|
file.IsReadOnly = false;
|
||||||
|
file.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get files count inside directory recursively
|
||||||
|
/// </summary>
|
||||||
|
public static int GetFilesCount(string filepath)
|
||||||
|
{
|
||||||
|
DirectoryInfo di = new DirectoryInfo(filepath);
|
||||||
|
return di.Exists ? di.GetFiles("*.*", SearchOption.AllDirectories).Length : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
project/Aki.Common/Utils/Zlib.cs
Normal file
127
project/Aki.Common/Utils/Zlib.cs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using ComponentAce.Compression.Libs.zlib;
|
||||||
|
|
||||||
|
namespace Aki.Common.Utils
|
||||||
|
{
|
||||||
|
public enum ZlibCompression
|
||||||
|
{
|
||||||
|
Store = 0,
|
||||||
|
Fastest = 1,
|
||||||
|
Fast = 3,
|
||||||
|
Normal = 5,
|
||||||
|
Ultra = 7,
|
||||||
|
Maximum = 9
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Zlib
|
||||||
|
{
|
||||||
|
// Level | CM/CI FLG
|
||||||
|
// ----- | ---------
|
||||||
|
// 1 | 78 01
|
||||||
|
// 2 | 78 5E
|
||||||
|
// 3 | 78 5E
|
||||||
|
// 4 | 78 5E
|
||||||
|
// 5 | 78 5E
|
||||||
|
// 6 | 78 9C
|
||||||
|
// 7 | 78 DA
|
||||||
|
// 8 | 78 DA
|
||||||
|
// 9 | 78 DA
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the file is ZLib compressed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Data">Data</param>
|
||||||
|
/// <returns>If the file is Zlib compressed</returns>
|
||||||
|
public static bool IsCompressed(byte[] Data)
|
||||||
|
{
|
||||||
|
// We need the first two bytes;
|
||||||
|
// First byte: Info (CM/CINFO) Header, should always be 0x78
|
||||||
|
// Second byte: Flags (FLG) Header, should define our compression level.
|
||||||
|
|
||||||
|
if (Data == null || Data.Length < 3 || Data[0] != 0x78)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Data[1])
|
||||||
|
{
|
||||||
|
case 0x01: // fastest
|
||||||
|
case 0x5E: // low
|
||||||
|
case 0x9C: // normal
|
||||||
|
case 0xDA: // max
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deflate data.
|
||||||
|
/// </summary>
|
||||||
|
public static byte[] Compress(byte[] data, ZlibCompression level)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[data.Length + 24];
|
||||||
|
|
||||||
|
ZStream zs = new ZStream()
|
||||||
|
{
|
||||||
|
avail_in = data.Length,
|
||||||
|
next_in = data,
|
||||||
|
next_in_index = 0,
|
||||||
|
avail_out = buffer.Length,
|
||||||
|
next_out = buffer,
|
||||||
|
next_out_index = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
zs.deflateInit((int)level);
|
||||||
|
zs.deflate(zlibConst.Z_FINISH);
|
||||||
|
|
||||||
|
data = new byte[zs.next_out_index];
|
||||||
|
Array.Copy(zs.next_out, 0, data, 0, zs.next_out_index);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inflate data.
|
||||||
|
/// </summary>
|
||||||
|
public static byte[] Decompress(byte[] data)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
|
||||||
|
ZStream zs = new ZStream()
|
||||||
|
{
|
||||||
|
avail_in = data.Length,
|
||||||
|
next_in = data,
|
||||||
|
next_in_index = 0,
|
||||||
|
avail_out = buffer.Length,
|
||||||
|
next_out = buffer,
|
||||||
|
next_out_index = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
zs.inflateInit();
|
||||||
|
|
||||||
|
using (MemoryStream ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
zs.avail_out = buffer.Length;
|
||||||
|
zs.next_out = buffer;
|
||||||
|
zs.next_out_index = 0;
|
||||||
|
|
||||||
|
int result = zs.inflate(0);
|
||||||
|
|
||||||
|
if (result != 0 && result != 1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ms.Write(zs.next_out, 0, zs.next_out_index);
|
||||||
|
}
|
||||||
|
while (zs.avail_in > 0 || zs.avail_out == 0);
|
||||||
|
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
project/Aki.Core/Aki.Core.csproj
Normal file
35
project/Aki.Core/Aki.Core.csproj
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net472</TargetFramework>
|
||||||
|
<AssemblyName>aki-core</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Company>Aki</Company>
|
||||||
|
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\Assembly-CSharp.dll" Private="False" />
|
||||||
|
<Reference Include="Assembly-CSharp-firstpass" HintPath="..\Shared\Managed\Assembly-CSharp-firstpass.dll" Private="False" />
|
||||||
|
<Reference Include="FilesChecker" HintPath="..\Shared\Managed\FilesChecker.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.UnityWebRequestModule" HintPath="..\Shared\Managed\UnityEngine.UnityWebRequestModule.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.UnityWebRequestTextureModule" HintPath="..\Shared\Managed\UnityEngine.UnityWebRequestTextureModule.dll" Private="False" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||||
|
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
33
project/Aki.Core/AkiCorePlugin.cs
Normal file
33
project/Aki.Core/AkiCorePlugin.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using Aki.Core.Patches;
|
||||||
|
using BepInEx;
|
||||||
|
|
||||||
|
namespace Aki.Core
|
||||||
|
{
|
||||||
|
[BepInPlugin("com.spt-aki.core", "AKI.Core", "1.0.0")]
|
||||||
|
class AkiCorePlugin : BaseUnityPlugin
|
||||||
|
{
|
||||||
|
public AkiCorePlugin()
|
||||||
|
{
|
||||||
|
Logger.LogInfo("Loading: Aki.Core");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
new ConsistencySinglePatch().Enable();
|
||||||
|
new ConsistencyMultiPatch().Enable();
|
||||||
|
new BattlEyePatch().Enable();
|
||||||
|
new SslCertificatePatch().Enable();
|
||||||
|
new UnityWebRequestPatch().Enable();
|
||||||
|
new WebSocketPatch().Enable();
|
||||||
|
new TransportPrefixPatch().Enable();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"{GetType().Name}: {ex}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInfo("Completed: Aki.Core");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
project/Aki.Core/Models/FakeCertificateHandler.cs
Normal file
13
project/Aki.Core/Models/FakeCertificateHandler.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using UnityEngine.Networking;
|
||||||
|
using Aki.Core.Utils;
|
||||||
|
|
||||||
|
namespace Aki.Core.Models
|
||||||
|
{
|
||||||
|
public class FakeCertificateHandler : CertificateHandler
|
||||||
|
{
|
||||||
|
protected override bool ValidateCertificate(byte[] certificateData)
|
||||||
|
{
|
||||||
|
return ValidationUtil.Validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
project/Aki.Core/Models/FakeFileCheckerResult.cs
Normal file
17
project/Aki.Core/Models/FakeFileCheckerResult.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using FilesChecker;
|
||||||
|
|
||||||
|
namespace Aki.Core.Models
|
||||||
|
{
|
||||||
|
public class FakeFileCheckerResult : ICheckResult
|
||||||
|
{
|
||||||
|
public TimeSpan ElapsedTime { get; private set; }
|
||||||
|
public Exception Exception { get; private set; }
|
||||||
|
|
||||||
|
public FakeFileCheckerResult()
|
||||||
|
{
|
||||||
|
ElapsedTime = new TimeSpan();
|
||||||
|
Exception = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
project/Aki.Core/Patches/BattlEyePatch.cs
Normal file
29
project/Aki.Core/Patches/BattlEyePatch.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using Aki.Core.Utils;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Aki.Core.Patches
|
||||||
|
{
|
||||||
|
public class BattlEyePatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var methodName = "RunValidation";
|
||||||
|
var flags = BindingFlags.Public | BindingFlags.Instance;
|
||||||
|
|
||||||
|
return PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null)
|
||||||
|
.GetMethod(methodName, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref Task __result, ref bool ___bool_0)
|
||||||
|
{
|
||||||
|
___bool_0 = ValidationUtil.Validate();
|
||||||
|
__result = Task.CompletedTask;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
project/Aki.Core/Patches/ConsistencyMultiPatch.cs
Normal file
26
project/Aki.Core/Patches/ConsistencyMultiPatch.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using Aki.Core.Models;
|
||||||
|
using FilesChecker;
|
||||||
|
|
||||||
|
namespace Aki.Core.Patches
|
||||||
|
{
|
||||||
|
public class ConsistencyMultiPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return PatchConstants.FilesCheckerTypes.Single(x => x.Name == "ConsistencyController")
|
||||||
|
.GetMethods().Single(x => x.Name == "EnsureConsistency" && x.ReturnType == typeof(Task<ICheckResult>));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref object __result)
|
||||||
|
{
|
||||||
|
__result = Task.FromResult<ICheckResult>(new FakeFileCheckerResult());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
project/Aki.Core/Patches/ConsistencySinglePatch.cs
Normal file
26
project/Aki.Core/Patches/ConsistencySinglePatch.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using Aki.Core.Models;
|
||||||
|
using FilesChecker;
|
||||||
|
|
||||||
|
namespace Aki.Core.Patches
|
||||||
|
{
|
||||||
|
public class ConsistencySinglePatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return PatchConstants.FilesCheckerTypes.Single(x => x.Name == "ConsistencyController")
|
||||||
|
.GetMethods().Single(x => x.Name == "EnsureConsistencySingle" && x.ReturnType == typeof(Task<ICheckResult>));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref object __result)
|
||||||
|
{
|
||||||
|
__result = Task.FromResult<ICheckResult>(new FakeFileCheckerResult());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
project/Aki.Core/Patches/DataHandlerDebugPatch.cs
Normal file
30
project/Aki.Core/Patches/DataHandlerDebugPatch.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using Aki.Core.Models;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using FilesChecker;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Aki.Core.Patches
|
||||||
|
{
|
||||||
|
public class DataHandlerDebugPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return PatchConstants.EftTypes
|
||||||
|
.Single(t => t.Name == "DataHandler")
|
||||||
|
.GetMethod("method_5", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPrefix(ref string __result)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"response json: ${__result}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
project/Aki.Core/Patches/SslCertificatePatch.cs
Normal file
25
project/Aki.Core/Patches/SslCertificatePatch.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using Aki.Core.Utils;
|
||||||
|
|
||||||
|
namespace Aki.Core.Patches
|
||||||
|
{
|
||||||
|
public class SslCertificatePatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return PatchConstants.EftTypes.Single(x => x.BaseType == typeof(CertificateHandler))
|
||||||
|
.GetMethod("ValidateCertificate", PatchConstants.PrivateFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref bool __result)
|
||||||
|
{
|
||||||
|
__result = ValidationUtil.Validate();
|
||||||
|
return false; // Skip origial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
project/Aki.Core/Patches/TransportPrefixPatch.cs
Normal file
75
project/Aki.Core/Patches/TransportPrefixPatch.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace Aki.Core.Patches
|
||||||
|
{
|
||||||
|
public class TransportPrefixPatch : ModulePatch
|
||||||
|
{
|
||||||
|
public TransportPrefixPatch()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var type = PatchConstants.EftTypes.Single(t => t.Name == "Class228");
|
||||||
|
var value = Traverse.Create(type).Field("TransportPrefixes").GetValue<Dictionary<ETransportProtocolType, string>>();
|
||||||
|
value[ETransportProtocolType.HTTPS] = "http://";
|
||||||
|
value[ETransportProtocolType.WSS] = "ws://";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"{nameof(TransportPrefixPatch)}: {ex}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return PatchConstants.EftTypes.Single(t => t.GetMethods().Any(m => m.Name == "CreateFromLegacyParams"))
|
||||||
|
.GetMethod("CreateFromLegacyParams", BindingFlags.Static | BindingFlags.Public);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref GStruct22 legacyParams)
|
||||||
|
{
|
||||||
|
//Console.WriteLine($"Original url {legacyParams.Url}");
|
||||||
|
legacyParams.Url = legacyParams.Url
|
||||||
|
.Replace("https://", "")
|
||||||
|
.Replace("http://", "");
|
||||||
|
//Console.WriteLine($"Edited url {legacyParams.Url}");
|
||||||
|
return true; // do original method after
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchTranspiler]
|
||||||
|
private static IEnumerable<CodeInstruction> PatchTranspile(ILGenerator generator, IEnumerable<CodeInstruction> instructions)
|
||||||
|
{
|
||||||
|
var codes = new List<CodeInstruction>(instructions);
|
||||||
|
|
||||||
|
var searchCode = new CodeInstruction(OpCodes.Ldstr, "https://");
|
||||||
|
var searchIndex = -1;
|
||||||
|
|
||||||
|
for (var i = 0; i < codes.Count; i++)
|
||||||
|
{
|
||||||
|
if (codes[i].opcode == searchCode.opcode && codes[i].operand == searchCode.operand)
|
||||||
|
{
|
||||||
|
searchIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchIndex == -1)
|
||||||
|
{
|
||||||
|
Logger.LogError($"{nameof(TransportPrefixPatch)} failed: Could not find reference code.");
|
||||||
|
return instructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
codes[searchIndex] = new CodeInstruction(OpCodes.Ldstr, "http://");
|
||||||
|
|
||||||
|
return codes.AsEnumerable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
project/Aki.Core/Patches/UnityWebRequestPatch.cs
Normal file
23
project/Aki.Core/Patches/UnityWebRequestPatch.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Core.Models;
|
||||||
|
|
||||||
|
namespace Aki.Core.Patches
|
||||||
|
{
|
||||||
|
public class UnityWebRequestPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(UnityWebRequestTexture).GetMethod(nameof(UnityWebRequestTexture.GetTexture), new[] { typeof(string) });
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(UnityWebRequest __result)
|
||||||
|
{
|
||||||
|
__result.certificateHandler = new FakeCertificateHandler();
|
||||||
|
__result.disposeCertificateHandlerOnDispose = true;
|
||||||
|
__result.timeout = 15000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
project/Aki.Core/Patches/WebSocketPatch.cs
Normal file
24
project/Aki.Core/Patches/WebSocketPatch.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Core.Patches
|
||||||
|
{
|
||||||
|
public class WebSocketPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var targetInterface = PatchConstants.EftTypes.Single(x => x == typeof(IConnectionHandler) && x.IsInterface);
|
||||||
|
var typeThatMatches = PatchConstants.EftTypes.Single(x => targetInterface.IsAssignableFrom(x) && x.IsAbstract && !x.IsInterface);
|
||||||
|
return typeThatMatches.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Single(x => x.ReturnType == typeof(Uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static Uri PatchPostfix(Uri __instance)
|
||||||
|
{
|
||||||
|
return new Uri(__instance.ToString().Replace("wss:", "ws:"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
project/Aki.Core/Utils/ValidationUtil.cs
Normal file
46
project/Aki.Core/Utils/ValidationUtil.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using Microsoft.Win32;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Aki.Core.Utils
|
||||||
|
{
|
||||||
|
public static class ValidationUtil
|
||||||
|
{
|
||||||
|
public static bool Validate()
|
||||||
|
{
|
||||||
|
var c0 = @"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov";
|
||||||
|
var v0 = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var v1 = Registry.LocalMachine.OpenSubKey(c0, false).GetValue("UninstallString");
|
||||||
|
var v2 = (v1 != null) ? v1.ToString() : string.Empty;
|
||||||
|
var v3 = new FileInfo(v2);
|
||||||
|
var v4 = new FileInfo[]
|
||||||
|
{
|
||||||
|
v3,
|
||||||
|
new FileInfo(v2.Replace(v3.Name, @"BattlEye\BEClient_x64.dll")),
|
||||||
|
new FileInfo(v2.Replace(v3.Name, @"BattlEye\BEService_x64.dll")),
|
||||||
|
new FileInfo(v2.Replace(v3.Name, @"ConsistencyInfo")),
|
||||||
|
new FileInfo(v2.Replace(v3.Name, @"Uninstall.exe")),
|
||||||
|
new FileInfo(v2.Replace(v3.Name, @"UnityCrashHandler64.exe"))
|
||||||
|
};
|
||||||
|
|
||||||
|
v0 = v4.Length - 1;
|
||||||
|
|
||||||
|
foreach (var value in v4)
|
||||||
|
{
|
||||||
|
if (File.Exists(value.FullName))
|
||||||
|
{
|
||||||
|
--v0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
v0 = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v0 == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
241
project/Aki.Custom/Airdrops/AirdropBox.cs
Normal file
241
project/Aki.Custom/Airdrops/AirdropBox.cs
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT.Airdrop;
|
||||||
|
using EFT.Interactive;
|
||||||
|
using EFT.SynchronizableObjects;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Airdrops
|
||||||
|
{
|
||||||
|
public class AirdropBox : MonoBehaviour
|
||||||
|
{
|
||||||
|
private const string CRATE_PATH = "assets/content/location_objects/lootable/prefab/scontainer_crate.bundle";
|
||||||
|
private const string AIRDROP_SOUNDS_PATH = "assets/content/audio/prefabs/airdrop/airdropsounds.bundle";
|
||||||
|
private readonly int CROSSFADE = Shader.PropertyToID("_Crossfade");
|
||||||
|
private readonly int COLLISION = Animator.StringToHash("collision");
|
||||||
|
|
||||||
|
public LootableContainer container;
|
||||||
|
private float fallSpeed;
|
||||||
|
private AirdropSynchronizableObject boxSync;
|
||||||
|
private AirdropLogicClass boxLogic;
|
||||||
|
private Material paraMaterial;
|
||||||
|
private Animator paraAnimator;
|
||||||
|
private AirdropSurfaceSet surfaceSet;
|
||||||
|
private Dictionary<BaseBallistic.ESurfaceSound, AirdropSurfaceSet> soundsDictionary;
|
||||||
|
private BetterSource audioSource;
|
||||||
|
|
||||||
|
private BetterSource AudioSource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (audioSource != null) return audioSource;
|
||||||
|
|
||||||
|
audioSource = Singleton<BetterAudio>.Instance.GetSource(BetterAudio.AudioSourceGroupType.Environment, false);
|
||||||
|
audioSource.transform.parent = transform;
|
||||||
|
audioSource.transform.localPosition = Vector3.up;
|
||||||
|
|
||||||
|
return audioSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<AirdropBox> Init(float crateFallSpeed)
|
||||||
|
{
|
||||||
|
var instance = (await LoadCrate()).AddComponent<AirdropBox>();
|
||||||
|
instance.soundsDictionary = await LoadSounds();
|
||||||
|
|
||||||
|
instance.container = instance.GetComponentInChildren<LootableContainer>();
|
||||||
|
|
||||||
|
instance.boxSync = instance.GetComponent<AirdropSynchronizableObject>();
|
||||||
|
instance.boxLogic = new AirdropLogicClass();
|
||||||
|
instance.boxSync.SetLogic(instance.boxLogic);
|
||||||
|
|
||||||
|
instance.paraAnimator = instance.boxSync.Parachute.GetComponent<Animator>();
|
||||||
|
instance.paraMaterial = instance.boxSync.Parachute.GetComponentInChildren<Renderer>().material;
|
||||||
|
instance.fallSpeed = crateFallSpeed;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<GameObject> LoadCrate()
|
||||||
|
{
|
||||||
|
var easyAssets = Singleton<PoolManager>.Instance.EasyAssets;
|
||||||
|
await easyAssets.Retain(CRATE_PATH, null, null).LoadingJob;
|
||||||
|
|
||||||
|
var crate = Instantiate(easyAssets.GetAsset<GameObject>(CRATE_PATH));
|
||||||
|
crate.SetActive(false);
|
||||||
|
return crate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<Dictionary<BaseBallistic.ESurfaceSound, AirdropSurfaceSet>> LoadSounds()
|
||||||
|
{
|
||||||
|
var easyAssets = Singleton<PoolManager>.Instance.EasyAssets;
|
||||||
|
await easyAssets.Retain(AIRDROP_SOUNDS_PATH, null, null).LoadingJob;
|
||||||
|
|
||||||
|
var soundsDictionary = new Dictionary<BaseBallistic.ESurfaceSound, AirdropSurfaceSet>();
|
||||||
|
var sets = easyAssets.GetAsset<AirdropSounds>(AIRDROP_SOUNDS_PATH).Sets;
|
||||||
|
foreach (var set in sets)
|
||||||
|
{
|
||||||
|
if (!soundsDictionary.ContainsKey(set.Surface))
|
||||||
|
{
|
||||||
|
soundsDictionary.Add(set.Surface, set);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError(set.Surface + " surface sounds are duplicated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return soundsDictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator DropCrate(Vector3 position)
|
||||||
|
{
|
||||||
|
RaycastBoxDistance(LayerMaskClass.TerrainLowPoly, out var hitInfo, position);
|
||||||
|
SetLandingSound();
|
||||||
|
boxSync.Init(1, position, Vector3.zero);
|
||||||
|
PlayAudioClip(boxSync.SqueakClip, true);
|
||||||
|
|
||||||
|
if(hitInfo.distance < 155f)
|
||||||
|
{
|
||||||
|
for (float i = 0; i < 1; i += Time.deltaTime / 6f)
|
||||||
|
{
|
||||||
|
transform.position = Vector3.Lerp(position, hitInfo.point, i*i);
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform.position = hitInfo.point;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var parachuteOpenPos = position + new Vector3(0f, -148.2f, 0f); // (5.5s * -9.8m/s^2) / 2
|
||||||
|
for (float i = 0; i < 1; i += Time.deltaTime / 5.5f)
|
||||||
|
{
|
||||||
|
transform.position = Vector3.Lerp(position, parachuteOpenPos, i * i);
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
OpenParachute();
|
||||||
|
while (RaycastBoxDistance(LayerMaskClass.TerrainLowPoly, out _))
|
||||||
|
{
|
||||||
|
transform.Translate(Vector3.down * (Time.deltaTime * fallSpeed));
|
||||||
|
transform.Rotate(Vector3.up, Time.deltaTime * 6f);
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
transform.position = hitInfo.point;
|
||||||
|
CloseParachute();
|
||||||
|
}
|
||||||
|
|
||||||
|
OnBoxLand(out var clipLength);
|
||||||
|
yield return new WaitForSecondsRealtime(clipLength + 0.5f);
|
||||||
|
ReleaseAudioSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBoxLand(out float clipLength)
|
||||||
|
{
|
||||||
|
var landingClip = surfaceSet.LandingSoundBank.PickSingleClip(surfaceSet.LandingSoundBank.GetRandomClipIndex(2));
|
||||||
|
clipLength = landingClip.length;
|
||||||
|
boxSync.AirdropDust.SetActive(true);
|
||||||
|
boxSync.AirdropDust.GetComponent<ParticleSystem>().Play();
|
||||||
|
AudioSource.source1.Stop();
|
||||||
|
PlayAudioClip(new TaggedClip
|
||||||
|
{
|
||||||
|
Clip = landingClip,
|
||||||
|
Falloff = (int)surfaceSet.LandingSoundBank.Rolloff,
|
||||||
|
Volume = surfaceSet.LandingSoundBank.BaseVolume
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool RaycastBoxDistance(LayerMask layerMask, out RaycastHit hitInfo)
|
||||||
|
{
|
||||||
|
return RaycastBoxDistance(layerMask, out hitInfo, transform.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool RaycastBoxDistance(LayerMask layerMask, out RaycastHit hitInfo, Vector3 origin)
|
||||||
|
{
|
||||||
|
var ray = new Ray(origin, Vector3.down);
|
||||||
|
|
||||||
|
var raycast = Physics.Raycast(ray, out hitInfo, Mathf.Infinity, layerMask);
|
||||||
|
if (!raycast) return false;
|
||||||
|
|
||||||
|
return hitInfo.distance > 0.05f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetLandingSound()
|
||||||
|
{
|
||||||
|
if (!RaycastBoxDistance(LayerMaskClass.AudioControllerStepLayerMask, out var raycast))
|
||||||
|
{
|
||||||
|
Debug.LogError("Raycast to ground returns no hit. Choose Concrete sound landing set");
|
||||||
|
surfaceSet = soundsDictionary[BaseBallistic.ESurfaceSound.Concrete];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (raycast.collider.TryGetComponent(out BaseBallistic component))
|
||||||
|
{
|
||||||
|
var surfaceSound = component.GetSurfaceSound(raycast.point);
|
||||||
|
if (soundsDictionary.ContainsKey(surfaceSound))
|
||||||
|
{
|
||||||
|
surfaceSet = soundsDictionary[surfaceSound];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
surfaceSet = soundsDictionary[BaseBallistic.ESurfaceSound.Concrete];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayAudioClip(TaggedClip clip, bool looped = false)
|
||||||
|
{
|
||||||
|
var volume = clip.Volume;
|
||||||
|
var occlusionGroupSimple = Singleton<BetterAudio>.Instance.GetOcclusionGroupSimple(transform.position, ref volume);
|
||||||
|
AudioSource.gameObject.SetActive(true);
|
||||||
|
AudioSource.source1.outputAudioMixerGroup = occlusionGroupSimple;
|
||||||
|
AudioSource.source1.spatialBlend = 1f;
|
||||||
|
AudioSource.SetRolloff(clip.Falloff);
|
||||||
|
AudioSource.source1.volume = volume;
|
||||||
|
|
||||||
|
if (AudioSource.source1.isPlaying) return;
|
||||||
|
|
||||||
|
AudioSource.source1.clip = clip.Clip;
|
||||||
|
AudioSource.source1.loop = looped;
|
||||||
|
AudioSource.source1.Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenParachute()
|
||||||
|
{
|
||||||
|
boxSync.Parachute.SetActive(true);
|
||||||
|
paraAnimator.SetBool(COLLISION, false);
|
||||||
|
StartCoroutine(CrossFadeAnimation(1f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseParachute()
|
||||||
|
{
|
||||||
|
paraAnimator.SetBool(COLLISION, true);
|
||||||
|
StartCoroutine(CrossFadeAnimation(0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator CrossFadeAnimation(float targetFadeValue)
|
||||||
|
{
|
||||||
|
var curFadeValue = paraMaterial.GetFloat(CROSSFADE);
|
||||||
|
for (float i = 0; i < 1; i += Time.deltaTime / 2f)
|
||||||
|
{
|
||||||
|
paraMaterial.SetFloat(CROSSFADE, Mathf.Lerp(curFadeValue, targetFadeValue, i*i));
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
paraMaterial.SetFloat(CROSSFADE, targetFadeValue);
|
||||||
|
|
||||||
|
if (targetFadeValue == 0f)
|
||||||
|
{
|
||||||
|
boxSync.Parachute.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReleaseAudioSource()
|
||||||
|
{
|
||||||
|
if (audioSource == null) return;
|
||||||
|
|
||||||
|
audioSource.transform.parent = null;
|
||||||
|
audioSource.Release();
|
||||||
|
audioSource = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
project/Aki.Custom/Airdrops/AirdropPlane.cs
Normal file
138
project/Aki.Custom/Airdrops/AirdropPlane.cs
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.Airdrop;
|
||||||
|
using EFT.SynchronizableObjects;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Airdrops
|
||||||
|
{
|
||||||
|
public class AirdropPlane : MonoBehaviour
|
||||||
|
{
|
||||||
|
private const string PLANE_PATH = "assets/content/location_objects/lootable/prefab/il76md-90.prefab";
|
||||||
|
private const float RADIUS_TO_PICK_RANDOM_POINT = 3000f;
|
||||||
|
|
||||||
|
private AirplaneSynchronizableObject airplaneSync;
|
||||||
|
private float speed;
|
||||||
|
private float distanceToDrop;
|
||||||
|
private float flaresCooldown;
|
||||||
|
private bool flaresDeployed;
|
||||||
|
private bool headingChanged;
|
||||||
|
|
||||||
|
public static async Task<AirdropPlane> Init(Vector3 airdropPoint, int dropHeight, float planeVolume, float speed)
|
||||||
|
{
|
||||||
|
var instance = (await LoadPlane()).AddComponent<AirdropPlane>();
|
||||||
|
|
||||||
|
instance.airplaneSync = instance.GetComponent<AirplaneSynchronizableObject>();
|
||||||
|
instance.airplaneSync.SetLogic(new AirplaneLogicClass());
|
||||||
|
|
||||||
|
instance.SetPosition(dropHeight, airdropPoint);
|
||||||
|
instance.SetAudio(planeVolume);
|
||||||
|
instance.speed = speed;
|
||||||
|
instance.gameObject.SetActive(false);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<GameObject> LoadPlane()
|
||||||
|
{
|
||||||
|
var easyAssets = Singleton<PoolManager>.Instance.EasyAssets;
|
||||||
|
await easyAssets.Retain(PLANE_PATH, null, null).LoadingJob;
|
||||||
|
var plane = Instantiate(easyAssets.GetAsset<GameObject>(PLANE_PATH));
|
||||||
|
return plane;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetAudio(float planeVolume)
|
||||||
|
{
|
||||||
|
var airplaneAudio = gameObject.AddComponent<AudioSource>();
|
||||||
|
airplaneAudio.clip = airplaneSync.soundClip.Clip;
|
||||||
|
|
||||||
|
airplaneAudio.dopplerLevel = 1f;
|
||||||
|
airplaneAudio.outputAudioMixerGroup = Singleton<BetterAudio>.Instance.VeryStandartMixerGroup;
|
||||||
|
airplaneAudio.loop = true;
|
||||||
|
airplaneAudio.maxDistance = 2000;
|
||||||
|
airplaneAudio.minDistance = 1;
|
||||||
|
airplaneAudio.pitch = 0.5f;
|
||||||
|
airplaneAudio.priority = 128;
|
||||||
|
airplaneAudio.reverbZoneMix = 1;
|
||||||
|
airplaneAudio.rolloffMode = AudioRolloffMode.Custom;
|
||||||
|
airplaneAudio.spatialBlend = 1;
|
||||||
|
airplaneAudio.spread = 60;
|
||||||
|
airplaneAudio.volume = planeVolume;
|
||||||
|
|
||||||
|
airplaneAudio.Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetPosition(int dropHeight, Vector3 airdropPoint)
|
||||||
|
{
|
||||||
|
var pointOnCircle = Random.insideUnitCircle.normalized * RADIUS_TO_PICK_RANDOM_POINT;
|
||||||
|
|
||||||
|
transform.position = new Vector3(pointOnCircle.x, dropHeight, pointOnCircle.y);
|
||||||
|
transform.LookAt(new Vector3(airdropPoint.x, dropHeight, airdropPoint.z));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ManualUpdate(float distance)
|
||||||
|
{
|
||||||
|
transform.Translate(Vector3.forward * (Time.deltaTime * speed));
|
||||||
|
distanceToDrop = distance;
|
||||||
|
UpdateFlaresLogic();
|
||||||
|
|
||||||
|
if (distance - 200f > 0f || headingChanged) return;
|
||||||
|
|
||||||
|
StartCoroutine(ChangeHeading());
|
||||||
|
headingChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateFlaresLogic()
|
||||||
|
{
|
||||||
|
if (flaresDeployed) return;
|
||||||
|
|
||||||
|
if (distanceToDrop > 0f && flaresCooldown <= Time.unscaledTime)
|
||||||
|
{
|
||||||
|
flaresCooldown = Time.unscaledTime + 4f;
|
||||||
|
StartCoroutine(DeployFlares(Random.Range(0.2f, 0.4f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distanceToDrop > 0f) return;
|
||||||
|
|
||||||
|
flaresDeployed = true;
|
||||||
|
StartCoroutine(DeployFlares(5f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator DeployFlares(float emissionTime)
|
||||||
|
{
|
||||||
|
var projectile = Instantiate(airplaneSync.infraredCountermeasureParticles, transform);
|
||||||
|
projectile.transform.localPosition = new Vector3(0f, -5f, 0f);
|
||||||
|
var flares = projectile.GetComponentsInChildren<ParticleSystem>();
|
||||||
|
var endTime = Time.unscaledTime + emissionTime;
|
||||||
|
Singleton<GameWorld>.Instance.SynchronizableObjectLogicProcessor.AirdropManager.AddProjectile(projectile,
|
||||||
|
endTime + flares[0].main.duration + flares[0].main.startLifetime.Evaluate(1f));
|
||||||
|
|
||||||
|
while (endTime > Time.unscaledTime)
|
||||||
|
yield return null;
|
||||||
|
|
||||||
|
projectile.transform.parent = null;
|
||||||
|
foreach (var particleSystem in flares)
|
||||||
|
particleSystem.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator ChangeHeading()
|
||||||
|
{
|
||||||
|
var startingRotation = transform.eulerAngles;
|
||||||
|
var middleRotation = startingRotation + new Vector3(0f, 40f, -200f);
|
||||||
|
var endRotation = middleRotation + new Vector3(0f, 40f, 200f);
|
||||||
|
|
||||||
|
for (float i = 0; i < 1; i += Time.deltaTime / 25f)
|
||||||
|
{
|
||||||
|
var finalRotation = Vector3.Lerp(middleRotation, endRotation, EasingSmoothSquared(i));
|
||||||
|
transform.eulerAngles = Vector3.Lerp(startingRotation, finalRotation, EasingSmoothSquared(i));
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float EasingSmoothSquared(float x)
|
||||||
|
{
|
||||||
|
return x < 0.5 ? x * x * 2 : (1 - (1 - x) * (1 - x) * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
107
project/Aki.Custom/Airdrops/AirdropsManager.cs
Normal file
107
project/Aki.Custom/Airdrops/AirdropsManager.cs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
using Aki.Custom.Airdrops.Models;
|
||||||
|
using Aki.Custom.Airdrops.Utils;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Airdrops
|
||||||
|
{
|
||||||
|
public class AirdropsManager : MonoBehaviour
|
||||||
|
{
|
||||||
|
private AirdropPlane airdropPlane;
|
||||||
|
private AirdropBox airdropBox;
|
||||||
|
private ItemFactoryUtil factory;
|
||||||
|
|
||||||
|
public bool isFlareDrop;
|
||||||
|
private AirdropParametersModel airdropParameters;
|
||||||
|
|
||||||
|
public async void Start()
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
|
||||||
|
if (gameWorld == null) Destroy(this);
|
||||||
|
|
||||||
|
airdropParameters = AirdropUtil.InitAirdropParams(gameWorld, isFlareDrop);
|
||||||
|
|
||||||
|
if (!airdropParameters.AirdropAvailable)
|
||||||
|
{
|
||||||
|
Destroy(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
airdropPlane = await AirdropPlane.Init(airdropParameters.RandomAirdropPoint,
|
||||||
|
airdropParameters.DropHeight, airdropParameters.Config.PlaneVolume, airdropParameters.Config.PlaneSpeed);
|
||||||
|
airdropBox = await AirdropBox.Init(airdropParameters.Config.CrateFallSpeed);
|
||||||
|
factory = new ItemFactoryUtil();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Debug.LogError($"[AKI-AIRDROPS]: Unable to create plane or crate, airdrop won't occur");
|
||||||
|
Destroy(this);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetDistanceToDrop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FixedUpdate()
|
||||||
|
{
|
||||||
|
airdropParameters.Timer += 0.02f;
|
||||||
|
|
||||||
|
if (airdropParameters.Timer >= airdropParameters.TimeToStart && !airdropParameters.PlaneSpawned)
|
||||||
|
{
|
||||||
|
StartPlane();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!airdropParameters.PlaneSpawned) return;
|
||||||
|
|
||||||
|
if (airdropParameters.DistanceTraveled >= airdropParameters.DistanceToDrop && !airdropParameters.BoxSpawned)
|
||||||
|
{
|
||||||
|
StartBox();
|
||||||
|
BuildLootContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (airdropParameters.DistanceTraveled < airdropParameters.DistanceToTravel)
|
||||||
|
{
|
||||||
|
airdropParameters.DistanceTraveled += Time.deltaTime * airdropParameters.Config.PlaneSpeed;
|
||||||
|
var distanceToDrop = airdropParameters.DistanceToDrop - airdropParameters.DistanceTraveled;
|
||||||
|
airdropPlane.ManualUpdate(distanceToDrop);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Destroy(airdropPlane.gameObject);
|
||||||
|
Destroy(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartPlane()
|
||||||
|
{
|
||||||
|
airdropPlane.gameObject.SetActive(true);
|
||||||
|
airdropParameters.PlaneSpawned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartBox()
|
||||||
|
{
|
||||||
|
airdropParameters.BoxSpawned = true;
|
||||||
|
var pointPos = airdropParameters.RandomAirdropPoint;
|
||||||
|
var dropPos = new Vector3(pointPos.x, airdropParameters.DropHeight, pointPos.z);
|
||||||
|
airdropBox.gameObject.SetActive(true);
|
||||||
|
airdropBox.StartCoroutine(airdropBox.DropCrate(dropPos));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildLootContainer()
|
||||||
|
{
|
||||||
|
factory.BuildContainer(airdropBox.container);
|
||||||
|
factory.AddLoot(airdropBox.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetDistanceToDrop()
|
||||||
|
{
|
||||||
|
airdropParameters.DistanceToDrop = Vector3.Distance(
|
||||||
|
new Vector3(airdropParameters.RandomAirdropPoint.x, airdropParameters.DropHeight, airdropParameters.RandomAirdropPoint.z),
|
||||||
|
airdropPlane.transform.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
project/Aki.Custom/Airdrops/Models/AirdropConfigModel.cs
Normal file
55
project/Aki.Custom/Airdrops/Models/AirdropConfigModel.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Airdrops.Models
|
||||||
|
{
|
||||||
|
public class AirdropConfigModel
|
||||||
|
{
|
||||||
|
[JsonProperty("airdropChancePercent")]
|
||||||
|
public AirdropChancePercent AirdropChancePercent { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("airdropMinStartTimeSeconds")]
|
||||||
|
public int AirdropMinStartTimeSeconds { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("airdropMaxStartTimeSeconds")]
|
||||||
|
public int AirdropMaxStartTimeSeconds { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("planeMinFlyHeight")]
|
||||||
|
public int PlaneMinFlyHeight { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("planeMaxFlyHeight")]
|
||||||
|
public int PlaneMaxFlyHeight { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("planeVolume")]
|
||||||
|
public float PlaneVolume { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("planeSpeed")]
|
||||||
|
public float PlaneSpeed { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("crateFallSpeed")]
|
||||||
|
public float CrateFallSpeed { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AirdropChancePercent
|
||||||
|
{
|
||||||
|
[JsonProperty("bigmap")]
|
||||||
|
public int Bigmap { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("woods")]
|
||||||
|
public int Woods { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("lighthouse")]
|
||||||
|
public int Lighthouse { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("shoreline")]
|
||||||
|
public int Shoreline { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("interchange")]
|
||||||
|
public int Interchange { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("reserve")]
|
||||||
|
public int Reserve { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("tarkovStreets")]
|
||||||
|
public int TarkovStreets { get; set; }
|
||||||
|
}
|
||||||
|
}
|
19
project/Aki.Custom/Airdrops/Models/AirdropLootModel.cs
Normal file
19
project/Aki.Custom/Airdrops/Models/AirdropLootModel.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Airdrops.Models
|
||||||
|
{
|
||||||
|
public class AirdropLootModel
|
||||||
|
{
|
||||||
|
[JsonProperty("tpl")]
|
||||||
|
public string Tpl { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("isPreset")]
|
||||||
|
public bool IsPreset { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("stackCount")]
|
||||||
|
public int StackCount { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public string ID { get; set; }
|
||||||
|
}
|
||||||
|
}
|
20
project/Aki.Custom/Airdrops/Models/AirdropParametersModel.cs
Normal file
20
project/Aki.Custom/Airdrops/Models/AirdropParametersModel.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
namespace Aki.Custom.Airdrops.Models
|
||||||
|
{
|
||||||
|
public class AirdropParametersModel
|
||||||
|
{
|
||||||
|
public AirdropConfigModel Config;
|
||||||
|
|
||||||
|
public bool AirdropAvailable;
|
||||||
|
public float DistanceTraveled;
|
||||||
|
public float DistanceToTravel;
|
||||||
|
public float DistanceToDrop;
|
||||||
|
public float Timer;
|
||||||
|
public bool PlaneSpawned;
|
||||||
|
public bool BoxSpawned;
|
||||||
|
|
||||||
|
public int DropHeight;
|
||||||
|
public int TimeToStart;
|
||||||
|
|
||||||
|
public UnityEngine.Vector3 RandomAirdropPoint;
|
||||||
|
}
|
||||||
|
}
|
32
project/Aki.Custom/Airdrops/Patches/AirdropFlarePatch.cs
Normal file
32
project/Aki.Custom/Airdrops/Patches/AirdropFlarePatch.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.Airdrop;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Airdrops.Patches
|
||||||
|
{
|
||||||
|
public class AirdropFlarePatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static readonly string[] _usableFlares = { "624c09cfbc2e27219346d955", "62389ba9a63f32501b1b4451" };
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(FlareCartridge).GetMethod(nameof(FlareCartridge.Init),
|
||||||
|
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(BulletClass flareCartridge)
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
var points = LocationScene.GetAll<AirdropPoint>().Any();
|
||||||
|
|
||||||
|
if (gameWorld != null && points && _usableFlares.Any(x => x == flareCartridge.Template._id))
|
||||||
|
{
|
||||||
|
gameWorld.gameObject.AddComponent<AirdropsManager>().isFlareDrop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
project/Aki.Custom/Airdrops/Patches/AirdropPatch.cs
Normal file
29
project/Aki.Custom/Airdrops/Patches/AirdropPatch.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.Airdrop;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Airdrops.Patches
|
||||||
|
{
|
||||||
|
public class AirdropPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(GameWorld).GetMethod(nameof(GameWorld.OnGameStarted));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void PatchPostFix()
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
var points = LocationScene.GetAll<AirdropPoint>().Any();
|
||||||
|
|
||||||
|
if (gameWorld != null && points)
|
||||||
|
{
|
||||||
|
gameWorld.gameObject.AddComponent<AirdropsManager>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
project/Aki.Custom/Airdrops/Utils/AirdropUtil.cs
Normal file
127
project/Aki.Custom/Airdrops/Utils/AirdropUtil.cs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Custom.Airdrops.Models;
|
||||||
|
using EFT;
|
||||||
|
using EFT.Airdrop;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
using Random = UnityEngine.Random;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Airdrops.Utils
|
||||||
|
{
|
||||||
|
public static class AirdropUtil
|
||||||
|
{
|
||||||
|
public static AirdropConfigModel GetConfigFromServer()
|
||||||
|
{
|
||||||
|
string json = RequestHandler.GetJson("/singleplayer/airdrop/config");
|
||||||
|
return JsonConvert.DeserializeObject<AirdropConfigModel>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ChanceToSpawn(GameWorld gameWorld, AirdropConfigModel config, bool isFlare)
|
||||||
|
{
|
||||||
|
// Flare summoned airdrops are guaranteed
|
||||||
|
if (isFlare)
|
||||||
|
{
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
string location = gameWorld.RegisteredPlayers[0].Location;
|
||||||
|
|
||||||
|
int result = 25;
|
||||||
|
switch (location.ToLower())
|
||||||
|
{
|
||||||
|
case "bigmap":
|
||||||
|
{
|
||||||
|
result = config.AirdropChancePercent.Bigmap;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "interchange":
|
||||||
|
{
|
||||||
|
result = config.AirdropChancePercent.Interchange;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "rezervbase":
|
||||||
|
{
|
||||||
|
result = config.AirdropChancePercent.Reserve;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "shoreline":
|
||||||
|
{
|
||||||
|
result = config.AirdropChancePercent.Shoreline;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "woods":
|
||||||
|
{
|
||||||
|
result = config.AirdropChancePercent.Woods;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "lighthouse":
|
||||||
|
{
|
||||||
|
result = config.AirdropChancePercent.Lighthouse;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "tarkovstreets":
|
||||||
|
{
|
||||||
|
result = config.AirdropChancePercent.TarkovStreets;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ShouldAirdropOccur(int dropChance, List<AirdropPoint> airdropPoints)
|
||||||
|
{
|
||||||
|
return airdropPoints.Count > 0 && Random.Range(0, 100) <= dropChance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AirdropParametersModel InitAirdropParams(GameWorld gameWorld, bool isFlare)
|
||||||
|
{
|
||||||
|
var serverConfig = GetConfigFromServer();
|
||||||
|
var allAirdropPoints = LocationScene.GetAll<AirdropPoint>().ToList();
|
||||||
|
var playerPosition = gameWorld.RegisteredPlayers[0].Position;
|
||||||
|
var flareAirdropPoints = new List<AirdropPoint>();
|
||||||
|
var dropChance = ChanceToSpawn(gameWorld, serverConfig, isFlare);
|
||||||
|
|
||||||
|
if (isFlare && allAirdropPoints.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (AirdropPoint point in allAirdropPoints)
|
||||||
|
{
|
||||||
|
if (Vector3.Distance(playerPosition, point.transform.position) <= 100f)
|
||||||
|
{
|
||||||
|
flareAirdropPoints.Add(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flareAirdropPoints.Count == 0 && isFlare)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[AKI-AIRDROPS]: Airdrop called in by flare, Unable to find an airdropPoint within 100m, defaulting to normal drop");
|
||||||
|
flareAirdropPoints.Add(allAirdropPoints.OrderBy(_ => Guid.NewGuid()).FirstOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AirdropParametersModel()
|
||||||
|
{
|
||||||
|
Config = serverConfig,
|
||||||
|
AirdropAvailable = ShouldAirdropOccur(dropChance, allAirdropPoints),
|
||||||
|
|
||||||
|
DistanceTraveled = 0f,
|
||||||
|
DistanceToTravel = 8000f,
|
||||||
|
Timer = 0,
|
||||||
|
PlaneSpawned = false,
|
||||||
|
BoxSpawned = false,
|
||||||
|
|
||||||
|
DropHeight = Random.Range(serverConfig.PlaneMinFlyHeight, serverConfig.PlaneMaxFlyHeight),
|
||||||
|
TimeToStart = isFlare
|
||||||
|
? 5
|
||||||
|
: Random.Range(serverConfig.AirdropMinStartTimeSeconds, serverConfig.AirdropMaxStartTimeSeconds),
|
||||||
|
|
||||||
|
RandomAirdropPoint = isFlare && allAirdropPoints.Count > 0
|
||||||
|
? flareAirdropPoints.OrderBy(_ => Guid.NewGuid()).First().transform.position
|
||||||
|
: allAirdropPoints.OrderBy(_ => Guid.NewGuid()).First().transform.position
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
project/Aki.Custom/Airdrops/Utils/ItemFactoryUtil.cs
Normal file
73
project/Aki.Custom/Airdrops/Utils/ItemFactoryUtil.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using UnityEngine;
|
||||||
|
using EFT.Interactive;
|
||||||
|
using EFT.InventoryLogic;
|
||||||
|
using Aki.Common.Http;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Aki.Custom.Airdrops.Models;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Airdrops.Utils
|
||||||
|
{
|
||||||
|
public class ItemFactoryUtil
|
||||||
|
{
|
||||||
|
private ItemFactory itemFactory;
|
||||||
|
private static readonly string DropContainer = "6223349b3136504a544d1608";
|
||||||
|
|
||||||
|
public ItemFactoryUtil()
|
||||||
|
{
|
||||||
|
itemFactory = Singleton<ItemFactory>.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BuildContainer(LootableContainer container)
|
||||||
|
{
|
||||||
|
if (itemFactory.ItemTemplates.TryGetValue(DropContainer, out var template))
|
||||||
|
{
|
||||||
|
Item item = itemFactory.CreateItem(DropContainer, template._id, null);
|
||||||
|
LootItem.CreateLootContainer(container, item, "CRATE", Singleton<GameWorld>.Instance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError($"[AKI-AIRDROPS]: unable to find template: {DropContainer}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void AddLoot(LootableContainer container)
|
||||||
|
{
|
||||||
|
List<AirdropLootModel> loot = GetLoot();
|
||||||
|
|
||||||
|
Item actualItem;
|
||||||
|
|
||||||
|
foreach (var item in loot)
|
||||||
|
{
|
||||||
|
ResourceKey[] resources;
|
||||||
|
if (item.IsPreset)
|
||||||
|
{
|
||||||
|
actualItem = itemFactory.GetPresetItem(item.Tpl);
|
||||||
|
resources = actualItem.GetAllItems().Select(x => x.Template).SelectMany(x => x.AllResources).ToArray();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actualItem = itemFactory.CreateItem(item.ID, item.Tpl, null);
|
||||||
|
actualItem.StackObjectsCount = item.StackCount;
|
||||||
|
|
||||||
|
resources = actualItem.Template.AllResources.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
container.ItemOwner.MainStorage[0].Add(actualItem);
|
||||||
|
await Singleton<PoolManager>.Instance.LoadBundlesAndCreatePools(PoolManager.PoolsCategory.Raid, PoolManager.AssemblyType.Local, resources, JobPriority.Immediate, null, PoolManager.DefaultCancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AirdropLootModel> GetLoot()
|
||||||
|
{
|
||||||
|
var json = RequestHandler.GetJson("/client/location/getAirdropLoot");
|
||||||
|
var loot = JsonConvert.DeserializeObject<List<AirdropLootModel>>(json);
|
||||||
|
|
||||||
|
return loot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
project/Aki.Custom/Aki.Custom.csproj
Normal file
42
project/Aki.Custom/Aki.Custom.csproj
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net472</TargetFramework>
|
||||||
|
<AssemblyName>aki-custom</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Company>Aki</Company>
|
||||||
|
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\Assembly-CSharp.dll" Private="False" />
|
||||||
|
<Reference Include="Comfort" HintPath="..\Shared\Managed\Comfort.dll" Private="False" />
|
||||||
|
<Reference Include="Sirenix.Serialization" HintPath="..\Shared\Managed\Sirenix.Serialization.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.AnimationModule" HintPath="..\Shared\Managed\UnityEngine.AnimationModule.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.AssetBundleModule" HintPath="..\Shared\Managed\UnityEngine.AssetBundleModule.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.AudioModule" HintPath="..\Shared\Managed\UnityEngine.AudioModule.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.ParticleSystemModule" HintPath="..\Shared\Managed\UnityEngine.ParticleSystemModule.dll" Private="false" />
|
||||||
|
<Reference Include="UnityEngine.PhysicsModule" HintPath="..\Shared\Managed\UnityEngine.PhysicsModule.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.UI" HintPath="..\Shared\Managed\UnityEngine.UI.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.UIModule" HintPath="..\Shared\Managed\UnityEngine.UIModule.dll" Private="False" />
|
||||||
|
<Reference Include="Unity.ScriptableBuildPipeline" HintPath="..\Shared\Managed\Unity.ScriptableBuildPipeline.dll" Private="False" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||||
|
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Aki.PrePatch\Aki.PrePatch.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
50
project/Aki.Custom/AkiCustomPlugin.cs
Normal file
50
project/Aki.Custom/AkiCustomPlugin.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using Aki.Custom.Airdrops.Patches;
|
||||||
|
using Aki.Custom.Patches;
|
||||||
|
using Aki.Custom.Utils;
|
||||||
|
using BepInEx;
|
||||||
|
|
||||||
|
namespace Aki.Custom
|
||||||
|
{
|
||||||
|
[BepInPlugin("com.spt-aki.custom", "AKI.Custom", "1.0.0")]
|
||||||
|
class AkiCustomPlugin : BaseUnityPlugin
|
||||||
|
{
|
||||||
|
public AkiCustomPlugin()
|
||||||
|
{
|
||||||
|
Logger.LogInfo("Loading: Aki.Custom");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Bundle patches should always load first
|
||||||
|
BundleManager.GetBundles();
|
||||||
|
new EasyAssetsPatch().Enable();
|
||||||
|
new EasyBundlePatch().Enable();
|
||||||
|
|
||||||
|
new BossSpawnChancePatch().Enable();
|
||||||
|
new BotDifficultyPatch().Enable();
|
||||||
|
new CoreDifficultyPatch().Enable();
|
||||||
|
new OfflineRaidMenuPatch().Enable();
|
||||||
|
new SessionIdPatch().Enable();
|
||||||
|
new VersionLabelPatch().Enable();
|
||||||
|
new IsEnemyPatch().Enable();
|
||||||
|
//new AddSelfAsEnemyPatch().Enable();
|
||||||
|
new CheckAndAddEnemyPatch().Enable();
|
||||||
|
new BotSelfEnemyPatch().Enable(); // needed
|
||||||
|
new AddEnemyToAllGroupsInBotZonePatch().Enable();
|
||||||
|
new AirdropPatch().Enable();
|
||||||
|
new AirdropFlarePatch().Enable();
|
||||||
|
new AddSptBotSettingsPatch().Enable();
|
||||||
|
new CustomAiPatch().Enable();
|
||||||
|
new ExitWhileLootingPatch().Enable();
|
||||||
|
new QTEPatch().Enable();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"{GetType().Name}: {ex}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInfo("Completed: Aki.Custom");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
project/Aki.Custom/Models/BundleInfo.cs
Normal file
16
project/Aki.Custom/Models/BundleInfo.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
namespace Aki.Custom.Models
|
||||||
|
{
|
||||||
|
public class BundleInfo
|
||||||
|
{
|
||||||
|
public string Key { get; }
|
||||||
|
public string Path { get; set; }
|
||||||
|
public string[] DependencyKeys { get; }
|
||||||
|
|
||||||
|
public BundleInfo(string key, string path, string[] dependencyKeys)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
Path = path;
|
||||||
|
DependencyKeys = dependencyKeys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
project/Aki.Custom/Models/BundleItem.cs
Normal file
9
project/Aki.Custom/Models/BundleItem.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Aki.Custom.Models
|
||||||
|
{
|
||||||
|
public struct BundleItem
|
||||||
|
{
|
||||||
|
public string FileName;
|
||||||
|
public uint Crc;
|
||||||
|
public string[] Dependencies;
|
||||||
|
}
|
||||||
|
}
|
24
project/Aki.Custom/Models/DefaultRaidSettings.cs
Normal file
24
project/Aki.Custom/Models/DefaultRaidSettings.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using EFT.Bots;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Models
|
||||||
|
{
|
||||||
|
public class DefaultRaidSettings
|
||||||
|
{
|
||||||
|
public EBotAmount AiAmount;
|
||||||
|
public EBotDifficulty AiDifficulty;
|
||||||
|
public bool BossEnabled;
|
||||||
|
public bool ScavWars;
|
||||||
|
public bool TaggedAndCursed;
|
||||||
|
public bool EnablePve;
|
||||||
|
|
||||||
|
public DefaultRaidSettings(EBotAmount aiAmount, EBotDifficulty aiDifficulty, bool bossEnabled, bool scavWars, bool taggedAndCursed, bool enablePve)
|
||||||
|
{
|
||||||
|
AiAmount = aiAmount;
|
||||||
|
AiDifficulty = aiDifficulty;
|
||||||
|
BossEnabled = bossEnabled;
|
||||||
|
ScavWars = scavWars;
|
||||||
|
TaggedAndCursed = taggedAndCursed;
|
||||||
|
EnablePve = enablePve;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
project/Aki.Custom/Models/VersionResponse.cs
Normal file
8
project/Aki.Custom/Models/VersionResponse.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Aki.Custom.Models
|
||||||
|
{
|
||||||
|
public struct VersionResponse
|
||||||
|
{
|
||||||
|
public string Version { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
34
project/Aki.Custom/Patches/AddEnemyPatch.cs
Normal file
34
project/Aki.Custom/Patches/AddEnemyPatch.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// If a bot being added has an ID found in list_1, it means its trying to add itself to its enemy list
|
||||||
|
/// Dont add bot to enemy list if its in list_1 and skip the rest of the AddEnemy() function
|
||||||
|
/// </summary>
|
||||||
|
public class AddSelfAsEnemyPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static readonly string methodName = "AddEnemy";
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(BotGroupClass).GetMethod(methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(BotGroupClass __instance, IAIDetails person)
|
||||||
|
{
|
||||||
|
var botOwners = (List<BotOwner>)__instance.GetType().GetField("list_1", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance);
|
||||||
|
if (botOwners.Any(x => x.Id == person.Id))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class AddEnemyToAllGroupsInBotZonePatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static Type _targetType;
|
||||||
|
private const string methodName = "AddEnemyToAllGroupsInBotZone";
|
||||||
|
|
||||||
|
public AddEnemyToAllGroupsInBotZonePatch()
|
||||||
|
{
|
||||||
|
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsTargetType(Type type)
|
||||||
|
{
|
||||||
|
if (type.Name == nameof(BotControllerClass) && type.GetMethod(methodName) != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {_targetType.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {methodName}");
|
||||||
|
|
||||||
|
return _targetType.GetMethod(methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AddEnemyToAllGroupsInBotZone()
|
||||||
|
/// Goal: by default, AddEnemyToAllGroupsInBotZone doesn't check if the bot group is on the same side as the player.
|
||||||
|
/// The effect of this is that when you are a Scav and kill a Usec, every bot group in the zone will aggro you including other Scavs.
|
||||||
|
/// This should fix that.
|
||||||
|
/// </summary>
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(BotControllerClass __instance, IAIDetails aggressor, IAIDetails groupOwner, IAIDetails target)
|
||||||
|
{
|
||||||
|
BotZone botZone = groupOwner.AIData.BotOwner.BotsGroup.BotZone;
|
||||||
|
foreach (var item in __instance.Groups())
|
||||||
|
{
|
||||||
|
if (item.Key != botZone)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var group in item.Value.GetGroups(notNull: true))
|
||||||
|
{
|
||||||
|
bool differentSide = aggressor.Side != group.Side;
|
||||||
|
bool sameSide = aggressor.Side == target.Side;
|
||||||
|
|
||||||
|
if (!group.Enemies.ContainsKey(aggressor)
|
||||||
|
&& (differentSide || !sameSide)
|
||||||
|
&& !group.HaveMemberWithRole(WildSpawnType.gifter)
|
||||||
|
&& group.ShallRevengeFor(target)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
group.AddEnemy(aggressor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
project/Aki.Custom/Patches/AddSptBotSettingsPatch.cs
Normal file
28
project/Aki.Custom/Patches/AddSptBotSettingsPatch.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using Aki.PrePatch;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class AddSptBotSettingsPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(BotSettingsRepoClass).GetMethod("Init");
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix(ref Dictionary<WildSpawnType, BotSettingsValuesClass> ___dictionary_0)
|
||||||
|
{
|
||||||
|
if (___dictionary_0.ContainsKey((WildSpawnType)AkiBotsPrePatcher.sptUsecValue))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
___dictionary_0.Add((WildSpawnType)AkiBotsPrePatcher.sptUsecValue, new BotSettingsValuesClass(false, false, false, EPlayerSide.Savage.ToStringNoBox()));
|
||||||
|
___dictionary_0.Add((WildSpawnType)AkiBotsPrePatcher.sptBearValue, new BotSettingsValuesClass(false, false, false, EPlayerSide.Savage.ToStringNoBox()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
project/Aki.Custom/Patches/BossSpawnChancePatch.cs
Normal file
56
project/Aki.Custom/Patches/BossSpawnChancePatch.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Boss spawn chance is 100%, all the time, this patch adjusts the chance to the maps boss wave value
|
||||||
|
/// </summary>
|
||||||
|
public class BossSpawnChancePatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static float[] _bossSpawnPercent;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var desiredType = PatchConstants.LocalGameType;
|
||||||
|
var desiredMethod = desiredType
|
||||||
|
.GetMethods(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly)
|
||||||
|
.SingleOrDefault(m => IsTargetMethod(m));
|
||||||
|
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsTargetMethod(MethodInfo mi)
|
||||||
|
{
|
||||||
|
var parameters = mi.GetParameters();
|
||||||
|
return (parameters.Length == 2
|
||||||
|
&& parameters[0].Name == "wavesSettings"
|
||||||
|
&& parameters[1].Name == "bossLocationSpawn");
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix(BossLocationSpawn[] bossLocationSpawn)
|
||||||
|
{
|
||||||
|
_bossSpawnPercent = bossLocationSpawn.Select(s => s.BossChance).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(ref BossLocationSpawn[] __result)
|
||||||
|
{
|
||||||
|
if (__result.Length != _bossSpawnPercent.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < _bossSpawnPercent.Length; i++)
|
||||||
|
{
|
||||||
|
__result[i].BossChance = _bossSpawnPercent[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
project/Aki.Custom/Patches/BotDifficultyPatch.cs
Normal file
28
project/Aki.Custom/Patches/BotDifficultyPatch.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using Aki.Common.Http;
|
||||||
|
using EFT;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class BotDifficultyPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var methodName = "LoadDifficultyStringInternal";
|
||||||
|
var flags = BindingFlags.Public | BindingFlags.Static;
|
||||||
|
|
||||||
|
return PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null)
|
||||||
|
.GetMethod(methodName, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref string __result, BotDifficulty botDifficulty, WildSpawnType role)
|
||||||
|
{
|
||||||
|
__result = RequestHandler.GetJson($"/singleplayer/settings/bot/difficulty/{role}/{botDifficulty}");
|
||||||
|
return string.IsNullOrWhiteSpace(__result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
project/Aki.Custom/Patches/BotEnemyTargetPatch.cs
Normal file
73
project/Aki.Custom/Patches/BotEnemyTargetPatch.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class BotEnemyTargetPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static Type _targetType;
|
||||||
|
private static readonly string methodName = "AddEnemyToAllGroupsInBotZone";
|
||||||
|
|
||||||
|
public BotEnemyTargetPatch()
|
||||||
|
{
|
||||||
|
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsTargetType(Type type)
|
||||||
|
{
|
||||||
|
if (type.Name == nameof(BotControllerClass) && type.GetMethod(methodName) != null)
|
||||||
|
{
|
||||||
|
Logger.LogInfo($"{methodName}: {type.FullName}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return _targetType.GetMethod(methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AddEnemyToAllGroupsInBotZone()
|
||||||
|
/// Goal: by default, AddEnemyToAllGroupsInBotZone doesn't check if the bot group is on the same side as the player.
|
||||||
|
/// The effect of this is that when you are a Scav and kill a Usec, every bot group in the zone will aggro you including other Scavs.
|
||||||
|
/// This should fix that.
|
||||||
|
/// </summary>
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(BotControllerClass __instance, IAIDetails aggressor, IAIDetails groupOwner, IAIDetails target)
|
||||||
|
{
|
||||||
|
BotZone botZone = groupOwner.AIData.BotOwner.BotsGroup.BotZone;
|
||||||
|
foreach (var item in __instance.Groups())
|
||||||
|
{
|
||||||
|
if (item.Key != botZone)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var group in item.Value.GetGroups(notNull: true))
|
||||||
|
{
|
||||||
|
if (!group.Enemies.ContainsKey(aggressor) && ShouldAttack(aggressor, target, group))
|
||||||
|
{
|
||||||
|
group.AddEnemy(aggressor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private static bool ShouldAttack(IAIDetails attacker, IAIDetails victim, BotGroupClass groupToCheck)
|
||||||
|
{
|
||||||
|
// Group should target if player attack a victim on the same side or if the group is not on the same side as the player.
|
||||||
|
bool shouldAttack = attacker.Side != groupToCheck.Side
|
||||||
|
|| attacker.Side == victim.Side;
|
||||||
|
|
||||||
|
return !groupToCheck.HaveMemberWithRole(WildSpawnType.gifter) && groupToCheck.ShallRevengeFor(victim) && shouldAttack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
project/Aki.Custom/Patches/BotSelfEnemyPatch.cs
Normal file
41
project/Aki.Custom/Patches/BotSelfEnemyPatch.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Goal: patch removes the current bot from its own enemy list - occurs when adding bots type to its enemy array in difficulty settings
|
||||||
|
/// </summary>
|
||||||
|
internal class BotSelfEnemyPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static readonly string methodName = "PreActivate";
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(BotOwner).GetMethod(methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(BotOwner __instance, BotGroupClass group)
|
||||||
|
{
|
||||||
|
IAIDetails selfToRemove = null;
|
||||||
|
|
||||||
|
foreach (var enemy in group.Enemies)
|
||||||
|
{
|
||||||
|
if (enemy.Key.Id == __instance.Id)
|
||||||
|
{
|
||||||
|
selfToRemove = enemy.Key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selfToRemove != null)
|
||||||
|
{
|
||||||
|
group.Enemies.Remove(selfToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
project/Aki.Custom/Patches/CheckAndAddEnemyPatch.cs
Normal file
73
project/Aki.Custom/Patches/CheckAndAddEnemyPatch.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class CheckAndAddEnemyPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static Type _targetType;
|
||||||
|
private static FieldInfo _sideField;
|
||||||
|
private static FieldInfo _enemiesField;
|
||||||
|
private static FieldInfo _spawnTypeField;
|
||||||
|
private static MethodInfo _addEnemy;
|
||||||
|
private readonly string _targetMethodName = "CheckAndAddEnemy";
|
||||||
|
|
||||||
|
public CheckAndAddEnemyPatch()
|
||||||
|
{
|
||||||
|
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
|
||||||
|
_sideField = _targetType.GetField("Side");
|
||||||
|
_enemiesField = _targetType.GetField("Enemies");
|
||||||
|
_spawnTypeField = _targetType.GetField("wildSpawnType_0", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
_addEnemy = _targetType.GetMethod("AddEnemy");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsTargetType(Type type)
|
||||||
|
{
|
||||||
|
if (type.GetMethod("AddEnemy") != null && type.GetMethod("AddEnemyGroupIfAllowed") != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return _targetType.GetMethod(_targetMethodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CheckAndAddEnemy()
|
||||||
|
/// Goal: This patch lets bosses shoot back once a PMC has shot them
|
||||||
|
/// removes the !player.AIData.IsAI check
|
||||||
|
/// </summary>
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(object __instance, ref IAIDetails player, ref bool ignoreAI)
|
||||||
|
{
|
||||||
|
//var side = (EPlayerSide)_sideField.GetValue(__instance);
|
||||||
|
//var botType = (WildSpawnType)_spawnTypeField.GetValue(__instance);
|
||||||
|
|
||||||
|
if (!player.HealthController.IsAlive)
|
||||||
|
{
|
||||||
|
return false; // do nothing and skip
|
||||||
|
}
|
||||||
|
|
||||||
|
var enemies = (Dictionary<IAIDetails, BotSettingsClass>)_enemiesField.GetValue(__instance);
|
||||||
|
if (enemies.ContainsKey(player))
|
||||||
|
{
|
||||||
|
return false;// do nothing and skip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add enemy to list
|
||||||
|
//if (!enemies.ContainsKey(player) && (!playerIsAi || ignoreAI))
|
||||||
|
_addEnemy.Invoke(__instance, new IAIDetails[] { player });
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
project/Aki.Custom/Patches/CoreDifficultyPatch.cs
Normal file
27
project/Aki.Custom/Patches/CoreDifficultyPatch.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using Aki.Common.Http;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class CoreDifficultyPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var methodName = "LoadCoreByString";
|
||||||
|
var flags = BindingFlags.Public | BindingFlags.Static;
|
||||||
|
|
||||||
|
return PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null)
|
||||||
|
.GetMethod(methodName, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref string __result)
|
||||||
|
{
|
||||||
|
__result = RequestHandler.GetJson("/singleplayer/settings/bot/difficulty/core/core");
|
||||||
|
return string.IsNullOrWhiteSpace(__result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
148
project/Aki.Custom/Patches/CustomAiPatch.cs
Normal file
148
project/Aki.Custom/Patches/CustomAiPatch.cs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Aki.PrePatch;
|
||||||
|
using Random = System.Random;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class CustomAiPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static readonly Random random = new Random();
|
||||||
|
private static Dictionary<WildSpawnType, Dictionary<string, Dictionary<string, int>>> botTypeCache = new Dictionary<WildSpawnType, Dictionary<string, Dictionary<string, int>>>();
|
||||||
|
private static DateTime cacheDate = new DateTime();
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(BotBrainClass).GetMethod("Activate", BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a randomly picked wildspawntype from server and change PMC bot to use it, this ensures the bot is generated with that random type altering its behaviour
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="__state">state to save for postfix to use later</param>
|
||||||
|
/// <param name="__instance"></param>
|
||||||
|
/// <param name="___botOwner_0">botOwner_0 property</param>
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(out WildSpawnType __state, object __instance, BotOwner ___botOwner_0)
|
||||||
|
{
|
||||||
|
// Store original type in state param
|
||||||
|
__state = ___botOwner_0.Profile.Info.Settings.Role;
|
||||||
|
//Console.WriteLine($"Processing bot {___botOwner_0.Profile.Info.Nickname} with role {___botOwner_0.Profile.Info.Settings.Role}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (BotIsSptPmc(___botOwner_0.Profile.Info.Settings.Role))
|
||||||
|
{
|
||||||
|
string currentMapName = GetCurrentMap();
|
||||||
|
|
||||||
|
if (!botTypeCache.TryGetValue(___botOwner_0.Profile.Info.Settings.Role, out var botSettings) || CacheIsStale())
|
||||||
|
{
|
||||||
|
ResetCacheDate();
|
||||||
|
HydrateCacheWithServerData();
|
||||||
|
|
||||||
|
if (!botTypeCache.TryGetValue(___botOwner_0.Profile.Info.Settings.Role, out botSettings))
|
||||||
|
{
|
||||||
|
throw new Exception($"Bots were refreshed from the server but the cache still doesnt contain an appropriate bot for type {___botOwner_0.Profile.Info.Settings.Role}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapSettings = botSettings[currentMapName.ToLower()];
|
||||||
|
var randomType = WeightedRandom(mapSettings.Keys.ToArray(), mapSettings.Values.ToArray());
|
||||||
|
if (Enum.TryParse(randomType, out WildSpawnType newAiType))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Updated spt bot {___botOwner_0.Profile.Info.Nickname}: {___botOwner_0.Profile.Info.Settings.Role} to {newAiType}");
|
||||||
|
___botOwner_0.Profile.Info.Settings.Role = newAiType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Couldnt not update spt bot {___botOwner_0.Profile.Info.Nickname} to the new type, random type {randomType} does not exist for WildSpawnType");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error processing log: {ex.Message}");
|
||||||
|
Console.WriteLine(ex.StackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // Do original
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Revert prefix change, get bots type back to what it was before changes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="__state">Saved state from prefix patch</param>
|
||||||
|
/// <param name="___botOwner_0">botOwner_0 property</param>
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostFix(WildSpawnType __state, BotOwner ___botOwner_0)
|
||||||
|
{
|
||||||
|
if (BotIsSptPmc(__state))
|
||||||
|
{
|
||||||
|
// Set spt bot bot back to original type
|
||||||
|
___botOwner_0.Profile.Info.Settings.Role = __state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool BotIsSptPmc(WildSpawnType role)
|
||||||
|
{
|
||||||
|
return (long)role == -2147483648 || (long)role == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetCurrentMap()
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
|
||||||
|
return gameWorld.RegisteredPlayers[0].Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CacheIsStale()
|
||||||
|
{
|
||||||
|
TimeSpan cacheAge = DateTime.Now - cacheDate;
|
||||||
|
|
||||||
|
return cacheAge.Minutes > 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ResetCacheDate()
|
||||||
|
{
|
||||||
|
cacheDate = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HydrateCacheWithServerData()
|
||||||
|
{
|
||||||
|
// Get weightings for PMCs from server and store in dict
|
||||||
|
var result = RequestHandler.GetJson($"/singleplayer/settings/bot/getBotBehaviours/");
|
||||||
|
botTypeCache = JsonConvert.DeserializeObject<Dictionary<WildSpawnType, Dictionary<string, Dictionary<string, int>>>>(result);
|
||||||
|
Console.WriteLine($"cached: {botTypeCache.Count} bots");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string WeightedRandom(string[] botTypes, int[] weights)
|
||||||
|
{
|
||||||
|
var cumulativeWeights = new int[botTypes.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < weights.Length; i++)
|
||||||
|
{
|
||||||
|
cumulativeWeights[i] = weights[i] + (i == 0 ? 0 : cumulativeWeights[i - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxCumulativeWeight = cumulativeWeights[cumulativeWeights.Length - 1];
|
||||||
|
var randomNumber = maxCumulativeWeight * random.NextDouble();
|
||||||
|
|
||||||
|
for (var itemIndex = 0; itemIndex < botTypes.Length; itemIndex++)
|
||||||
|
{
|
||||||
|
if (cumulativeWeights[itemIndex] >= randomNumber)
|
||||||
|
{
|
||||||
|
return botTypes[itemIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("failed to get random bot weighting, returned assault");
|
||||||
|
return "assault";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
137
project/Aki.Custom/Patches/EasyAssetsPatch.cs
Normal file
137
project/Aki.Custom/Patches/EasyAssetsPatch.cs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using Diz.Jobs;
|
||||||
|
using Diz.Resources;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Build.Pipeline;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Aki.Custom.Models;
|
||||||
|
using Aki.Custom.Utils;
|
||||||
|
using DependencyGraph = DependencyGraph<IEasyBundle>;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class EasyAssetsPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static readonly FieldInfo _manifestField;
|
||||||
|
private static readonly FieldInfo _bundlesField;
|
||||||
|
private static readonly PropertyInfo _systemProperty;
|
||||||
|
|
||||||
|
static EasyAssetsPatch()
|
||||||
|
{
|
||||||
|
var type = typeof(EasyAssets);
|
||||||
|
|
||||||
|
_manifestField = type.GetField(nameof(EasyAssets.Manifest));
|
||||||
|
_bundlesField = type.GetField($"{EasyBundleHelper.Type.Name.ToLowerInvariant()}_0", PatchConstants.PrivateFlags);
|
||||||
|
_systemProperty = type.GetProperty("System");
|
||||||
|
}
|
||||||
|
|
||||||
|
public EasyAssetsPatch()
|
||||||
|
{
|
||||||
|
_ = nameof(IEasyBundle.SameNameAsset);
|
||||||
|
_ = nameof(IBundleLock.IsLocked);
|
||||||
|
_ = nameof(BundleLock.MaxConcurrentOperations);
|
||||||
|
_ = nameof(DependencyGraph.GetDefaultNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(EasyAssets).GetMethods(PatchConstants.PrivateFlags).Single(IsTargetMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsTargetMethod(MethodInfo mi)
|
||||||
|
{
|
||||||
|
var parameters = mi.GetParameters();
|
||||||
|
return (parameters.Length == 6
|
||||||
|
&& parameters[0].Name == "bundleLock"
|
||||||
|
&& parameters[1].Name == "defaultKey"
|
||||||
|
&& parameters[4].Name == "shouldExclude");
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref Task __result, EasyAssets __instance, [CanBeNull] IBundleLock bundleLock, string defaultKey, string rootPath,
|
||||||
|
string platformName, [CanBeNull] Func<string, bool> shouldExclude, [CanBeNull] Func<string, Task> bundleCheck)
|
||||||
|
{
|
||||||
|
__result = Init(__instance, bundleLock, defaultKey, rootPath, platformName, shouldExclude, bundleCheck);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetPairKey(KeyValuePair<string, BundleItem> x)
|
||||||
|
{
|
||||||
|
return x.Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BundleDetails GetPairValue(KeyValuePair<string, BundleItem> x)
|
||||||
|
{
|
||||||
|
return new BundleDetails
|
||||||
|
{
|
||||||
|
FileName = x.Value.FileName,
|
||||||
|
Crc = x.Value.Crc,
|
||||||
|
Dependencies = x.Value.Dependencies
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<CompatibilityAssetBundleManifest> GetManifestBundle(string filepath)
|
||||||
|
{
|
||||||
|
var manifestLoading = AssetBundle.LoadFromFileAsync(filepath);
|
||||||
|
await manifestLoading.Await();
|
||||||
|
|
||||||
|
var assetBundle = manifestLoading.assetBundle;
|
||||||
|
var assetLoading = assetBundle.LoadAllAssetsAsync();
|
||||||
|
await assetLoading.Await();
|
||||||
|
|
||||||
|
return (CompatibilityAssetBundleManifest)assetLoading.allAssets[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<CompatibilityAssetBundleManifest> GetManifestJson(string filepath)
|
||||||
|
{
|
||||||
|
var text = string.Empty;
|
||||||
|
|
||||||
|
using (var reader = File.OpenText($"{filepath}.json"))
|
||||||
|
{
|
||||||
|
text = await reader.ReadToEndAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = JsonConvert.DeserializeObject<Dictionary<string, BundleItem>>(text).ToDictionary(GetPairKey, GetPairValue);
|
||||||
|
var manifest = ScriptableObject.CreateInstance<CompatibilityAssetBundleManifest>();
|
||||||
|
manifest.SetResults(data);
|
||||||
|
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task Init(EasyAssets instance, [CanBeNull] IBundleLock bundleLock, string defaultKey, string rootPath,
|
||||||
|
string platformName, [CanBeNull] Func<string, bool> shouldExclude, Func<string, Task> bundleCheck)
|
||||||
|
{
|
||||||
|
// platform manifest
|
||||||
|
var path = $"{rootPath.Replace("file:///", string.Empty).Replace("file://", string.Empty)}/{platformName}/";
|
||||||
|
var filepath = path + platformName;
|
||||||
|
var manifest = (File.Exists(filepath)) ? await GetManifestBundle(filepath) : await GetManifestJson(filepath);
|
||||||
|
|
||||||
|
// load bundles
|
||||||
|
var bundleNames = manifest.GetAllAssetBundles().Union(BundleManager.Bundles.Keys).ToArray();
|
||||||
|
var bundles = (IEasyBundle[])Array.CreateInstance(EasyBundleHelper.Type, bundleNames.Length);
|
||||||
|
|
||||||
|
if (bundleLock == null)
|
||||||
|
{
|
||||||
|
bundleLock = new BundleLock(int.MaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < bundleNames.Length; i++)
|
||||||
|
{
|
||||||
|
bundles[i] = (IEasyBundle)Activator.CreateInstance(EasyBundleHelper.Type, new object[] { bundleNames[i], path, manifest, bundleLock, bundleCheck });
|
||||||
|
await JobScheduler.Yield(EJobPriority.Immediate);
|
||||||
|
}
|
||||||
|
|
||||||
|
_manifestField.SetValue(instance, manifest);
|
||||||
|
_bundlesField.SetValue(instance, bundles);
|
||||||
|
_systemProperty.SetValue(instance, new DependencyGraph(bundles, defaultKey, shouldExclude));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
project/Aki.Custom/Patches/EasyBundlePatch.cs
Normal file
49
project/Aki.Custom/Patches/EasyBundlePatch.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Diz.DependencyManager;
|
||||||
|
using UnityEngine.Build.Pipeline;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Aki.Custom.Models;
|
||||||
|
using Aki.Custom.Utils;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class EasyBundlePatch : ModulePatch
|
||||||
|
{
|
||||||
|
static EasyBundlePatch()
|
||||||
|
{
|
||||||
|
_ = nameof(IEasyBundle.SameNameAsset);
|
||||||
|
_ = nameof(IBundleLock.IsLocked);
|
||||||
|
_ = nameof(BindableState<ELoadState>.Bind);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return EasyBundleHelper.Type.GetConstructors()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(object __instance, string key, string rootPath, CompatibilityAssetBundleManifest manifest, IBundleLock bundleLock)
|
||||||
|
{
|
||||||
|
var path = rootPath + key;
|
||||||
|
var dependencyKeys = manifest.GetDirectDependencies(key) ?? new string[0];
|
||||||
|
|
||||||
|
if (BundleManager.Bundles.TryGetValue(key, out BundleInfo bundle))
|
||||||
|
{
|
||||||
|
dependencyKeys = (dependencyKeys.Length > 0) ? dependencyKeys.Union(bundle.DependencyKeys).ToArray() : bundle.DependencyKeys;
|
||||||
|
path = bundle.Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = new EasyBundleHelper(__instance)
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
Path = path,
|
||||||
|
KeyWithoutExtension = Path.GetFileNameWithoutExtension(key),
|
||||||
|
DependencyKeys = dependencyKeys,
|
||||||
|
LoadState = new BindableState<ELoadState>(ELoadState.Unloaded, null),
|
||||||
|
BundleLock = bundleLock
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
project/Aki.Custom/Patches/ExitWhileLootingPatch.cs
Normal file
42
project/Aki.Custom/Patches/ExitWhileLootingPatch.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fix a bsg bug that causes the game to soft-lock when you have a container opened when extracting
|
||||||
|
/// </summary>
|
||||||
|
public class ExitWhileLootingPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return PatchConstants.EftTypes.Single(x => x.Name == "LocalGame").BaseType // BaseLocalGame
|
||||||
|
.GetMethod("Stop", BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look at BaseLocalGame<TPlayerOwner> and find a method named "Stop"
|
||||||
|
// once you find it, there should be a StartBlackScreenShow method with
|
||||||
|
// a callback method (on dnspy will be called @class.method_0)
|
||||||
|
// Go into that method. This will be part of the code:
|
||||||
|
// if (GClass2505.CheckCurrentScreen(EScreenType.Reconnect))
|
||||||
|
// {
|
||||||
|
// GClass2505.CloseAllScreensForced();
|
||||||
|
// }
|
||||||
|
// The code INSIDE the if needs to run
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(string profileId)
|
||||||
|
{
|
||||||
|
var player = Singleton<GameWorld>.Instance.MainPlayer;
|
||||||
|
if (profileId == player?.Profile.Id)
|
||||||
|
{
|
||||||
|
GClass2727.Instance.CloseAllScreensForced();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
121
project/Aki.Custom/Patches/IsEnemyPatch.cs
Normal file
121
project/Aki.Custom/Patches/IsEnemyPatch.cs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class IsEnemyPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static Type _targetType;
|
||||||
|
private readonly string _targetMethodName = "IsEnemy";
|
||||||
|
|
||||||
|
public IsEnemyPatch()
|
||||||
|
{
|
||||||
|
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsTargetType(Type type)
|
||||||
|
{
|
||||||
|
if (type.GetMethod("AddEnemy") != null && type.GetMethod("AddEnemyGroupIfAllowed") != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return _targetType.GetMethod(_targetMethodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IsEnemy()
|
||||||
|
/// Goal: Make bots take Side into account when deciding if another player/bot is an enemy
|
||||||
|
/// Check enemy cache list first, if not found, check side, if they differ, add to enemy list and return true
|
||||||
|
/// Needed to ensure bot checks the enemy side, not just its botType
|
||||||
|
/// </summary>
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref bool __result, BotGroupClass __instance, IAIDetails requester)
|
||||||
|
{
|
||||||
|
var isEnemy = false; // default not an enemy
|
||||||
|
|
||||||
|
// Check existing enemies list
|
||||||
|
if (__instance.Enemies.Any(x=> x.Value.Player.Id == requester.Id))
|
||||||
|
{
|
||||||
|
isEnemy = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (__instance.Side == EPlayerSide.Usec)
|
||||||
|
{
|
||||||
|
if (requester.Side == EPlayerSide.Bear || requester.Side == EPlayerSide.Savage ||
|
||||||
|
ShouldAttackUsec(requester))
|
||||||
|
{
|
||||||
|
isEnemy = true;
|
||||||
|
__instance.AddEnemy(requester);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (__instance.Side == EPlayerSide.Bear)
|
||||||
|
{
|
||||||
|
if (requester.Side == EPlayerSide.Usec || requester.Side == EPlayerSide.Savage ||
|
||||||
|
ShouldAttackBear(requester))
|
||||||
|
{
|
||||||
|
isEnemy = true;
|
||||||
|
__instance.AddEnemy(requester);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (__instance.Side == EPlayerSide.Savage)
|
||||||
|
{
|
||||||
|
if (requester.Side != EPlayerSide.Savage)
|
||||||
|
{
|
||||||
|
// everyone else is an enemy to savage (scavs)
|
||||||
|
isEnemy = true;
|
||||||
|
__instance.AddEnemy(requester);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__result = isEnemy;
|
||||||
|
|
||||||
|
return true; // Skip original
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return True when usec default behavior is attack + bot is usec
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requester"></param>
|
||||||
|
/// <returns>bool</returns>
|
||||||
|
private static bool ShouldAttackUsec(IAIDetails requester)
|
||||||
|
{
|
||||||
|
var requesterMind = requester?.AIData?.BotOwner?.Settings?.FileSettings?.Mind;
|
||||||
|
|
||||||
|
if (requesterMind == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requester.IsAI && requesterMind.DEFAULT_USEC_BEHAVIOUR == EWarnBehaviour.Attack && requester.Side == EPlayerSide.Usec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return True when bear default behavior is attack + bot is bear
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requester"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static bool ShouldAttackBear(IAIDetails requester)
|
||||||
|
{
|
||||||
|
var requesterMind = requester.AIData?.BotOwner?.Settings?.FileSettings?.Mind;
|
||||||
|
|
||||||
|
if (requesterMind == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requester.IsAI && requesterMind.DEFAULT_BEAR_BEHAVIOUR == EWarnBehaviour.Attack && requester.Side == EPlayerSide.Bear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
project/Aki.Custom/Patches/OfflineRaidMenuPatch.cs
Normal file
69
project/Aki.Custom/Patches/OfflineRaidMenuPatch.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Common.Utils;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Custom.Models;
|
||||||
|
using EFT.UI;
|
||||||
|
using EFT.UI.Matchmaker;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using EFT;
|
||||||
|
using static EFT.UI.Matchmaker.MatchmakerOfflineRaidScreen;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class OfflineRaidMenuPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var desiredType = typeof(MatchmakerOfflineRaidScreen);
|
||||||
|
var desiredMethod = desiredType.GetMethod(nameof(MatchmakerOfflineRaidScreen.Show));
|
||||||
|
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix(GClass2769 controller, UpdatableToggle ____offlineModeToggle)
|
||||||
|
{
|
||||||
|
var raidSettings = controller.RaidSettings;
|
||||||
|
|
||||||
|
raidSettings.RaidMode = ERaidMode.Local;
|
||||||
|
raidSettings.BotSettings.IsEnabled = true;
|
||||||
|
|
||||||
|
// Default checkbox to be ticked
|
||||||
|
____offlineModeToggle.isOn = true;
|
||||||
|
|
||||||
|
// get settings from server
|
||||||
|
var json = RequestHandler.GetJson("/singleplayer/settings/raid/menu");
|
||||||
|
var settings = Json.Deserialize<DefaultRaidSettings>(json);
|
||||||
|
|
||||||
|
// TODO: Not all settings are used and they also don't cover all the new settings that are available client-side
|
||||||
|
if (settings != null)
|
||||||
|
{
|
||||||
|
raidSettings.BotSettings.BotAmount = settings.AiAmount;
|
||||||
|
raidSettings.WavesSettings.BotAmount = settings.AiAmount;
|
||||||
|
|
||||||
|
raidSettings.WavesSettings.BotDifficulty = settings.AiDifficulty;
|
||||||
|
|
||||||
|
raidSettings.WavesSettings.IsBosses = settings.BossEnabled;
|
||||||
|
|
||||||
|
raidSettings.BotSettings.IsScavWars = false;
|
||||||
|
|
||||||
|
raidSettings.WavesSettings.IsTaggedAndCursed = settings.TaggedAndCursed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix()
|
||||||
|
{
|
||||||
|
// disable "no progression save" panel
|
||||||
|
var offlineRaidScreenContent = GameObject.Find("Matchmaker Offline Raid Screen").transform.Find("Content").transform;
|
||||||
|
var warningPanel = offlineRaidScreenContent.Find("WarningPanelHorLayout");
|
||||||
|
warningPanel.gameObject.SetActive(false);
|
||||||
|
var spacer = offlineRaidScreenContent.Find("Space (1)");
|
||||||
|
spacer.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
project/Aki.Custom/Patches/QTEPatch.cs
Normal file
25
project/Aki.Custom/Patches/QTEPatch.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using System.Reflection;
|
||||||
|
using EFT;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class QTEPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod() => typeof(HideoutPlayerOwner).GetMethod(nameof(HideoutPlayerOwner.StopWorkout));
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(HideoutPlayerOwner __instance)
|
||||||
|
{
|
||||||
|
RequestHandler.PutJson("/client/hideout/workout", new
|
||||||
|
{
|
||||||
|
skills = __instance.HideoutPlayer.Skills,
|
||||||
|
effects = __instance.HideoutPlayer.HealthController.BodyPartEffects
|
||||||
|
}
|
||||||
|
.ToJson());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
project/Aki.Custom/Patches/SessionIdPatch.cs
Normal file
39
project/Aki.Custom/Patches/SessionIdPatch.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT.UI;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class SessionIdPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static PreloaderUI _preloader;
|
||||||
|
|
||||||
|
static SessionIdPatch()
|
||||||
|
{
|
||||||
|
_preloader = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return PatchConstants.LocalGameType.BaseType.GetMethod("method_5", PatchConstants.PrivateFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix()
|
||||||
|
{
|
||||||
|
if (_preloader == null)
|
||||||
|
{
|
||||||
|
_preloader = Object.FindObjectOfType<PreloaderUI>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_preloader != null)
|
||||||
|
{
|
||||||
|
var raidID = Path.GetRandomFileName().Replace(".", string.Empty).Substring(0, 6).ToUpperInvariant();
|
||||||
|
_preloader.SetSessionId(raidID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
project/Aki.Custom/Patches/VersionLabelPatch.cs
Normal file
48
project/Aki.Custom/Patches/VersionLabelPatch.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Common.Utils;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using Aki.Custom.Models;
|
||||||
|
using EFT.UI;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class VersionLabelPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static string _versionLabel;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return PatchConstants.EftTypes
|
||||||
|
.Single(x => x.GetField("Taxonomy", BindingFlags.Public | BindingFlags.Instance) != null)
|
||||||
|
.GetMethod("Create", BindingFlags.Public | BindingFlags.Static);
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogInfo($"VersionLabelPatch failed {e.Message} {e.StackTrace} {e.InnerException.StackTrace}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
internal static void PatchPostfix(object __result)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_versionLabel))
|
||||||
|
{
|
||||||
|
var json = RequestHandler.GetJson("/singleplayer/settings/version");
|
||||||
|
_versionLabel = Json.Deserialize<VersionResponse>(json).Version;
|
||||||
|
Logger.LogInfo($"Server version: {_versionLabel}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Traverse.Create(MonoBehaviourSingleton<PreloaderUI>.Instance).Field("_alphaVersionLabel").Property("LocalizationKey").SetValue("{0}");
|
||||||
|
Traverse.Create(MonoBehaviourSingleton<PreloaderUI>.Instance).Field("string_2").SetValue(_versionLabel);
|
||||||
|
Traverse.Create(__result).Field("Major").SetValue(_versionLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
project/Aki.Custom/Utils/BundleManager.cs
Normal file
50
project/Aki.Custom/Utils/BundleManager.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Common.Utils;
|
||||||
|
using Aki.Custom.Models;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Utils
|
||||||
|
{
|
||||||
|
public static class BundleManager
|
||||||
|
{
|
||||||
|
public const string CachePath = "user/cache/bundles/";
|
||||||
|
public static Dictionary<string, BundleInfo> Bundles { get; private set; }
|
||||||
|
|
||||||
|
static BundleManager()
|
||||||
|
{
|
||||||
|
Bundles = new Dictionary<string, BundleInfo>();
|
||||||
|
|
||||||
|
if (VFS.Exists(CachePath))
|
||||||
|
{
|
||||||
|
VFS.DeleteDirectory(CachePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GetBundles()
|
||||||
|
{
|
||||||
|
var json = RequestHandler.GetJson("/singleplayer/bundles");
|
||||||
|
var jArray = JArray.Parse(json);
|
||||||
|
|
||||||
|
foreach (var jObj in jArray)
|
||||||
|
{
|
||||||
|
var key = jObj["key"].ToString();
|
||||||
|
var path = jObj["path"].ToString();
|
||||||
|
var bundle = new BundleInfo(key, path, jObj["dependencyKeys"].ToObject<string[]>());
|
||||||
|
|
||||||
|
if (path.Contains("http"))
|
||||||
|
{
|
||||||
|
var filepath = CachePath + Regex.Split(path, "bundle/", RegexOptions.IgnoreCase)[1];
|
||||||
|
var data = RequestHandler.GetData(path, true);
|
||||||
|
VFS.WriteFile(filepath, data);
|
||||||
|
bundle.Path = filepath;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bundles.Add(key, bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
VFS.WriteTextFile(CachePath + "bundles.json", Json.Serialize<Dictionary<string, BundleInfo>>(Bundles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
121
project/Aki.Custom/Utils/EasyBundleHelper.cs
Normal file
121
project/Aki.Custom/Utils/EasyBundleHelper.cs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using BindableState = BindableState<Diz.DependencyManager.ELoadState>;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Utils
|
||||||
|
{
|
||||||
|
public class EasyBundleHelper
|
||||||
|
{
|
||||||
|
private const BindingFlags _flags = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||||
|
private static readonly FieldInfo _pathField;
|
||||||
|
private static readonly FieldInfo _keyWithoutExtensionField;
|
||||||
|
private static readonly FieldInfo _bundleLockField;
|
||||||
|
private static readonly PropertyInfo _dependencyKeysProperty;
|
||||||
|
private static readonly PropertyInfo _keyProperty;
|
||||||
|
private static readonly PropertyInfo _loadStateProperty;
|
||||||
|
private static readonly MethodInfo _loadingCoroutineMethod;
|
||||||
|
private readonly object _instance;
|
||||||
|
public static readonly Type Type;
|
||||||
|
|
||||||
|
static EasyBundleHelper()
|
||||||
|
{
|
||||||
|
_ = nameof(IBundleLock.IsLocked);
|
||||||
|
_ = nameof(BindableState.Bind);
|
||||||
|
|
||||||
|
Type = PatchConstants.EftTypes.Single(x => x.GetMethod("set_SameNameAsset", _flags) != null);
|
||||||
|
_pathField = Type.GetField("string_1", _flags);
|
||||||
|
_keyWithoutExtensionField = Type.GetField("string_0", _flags);
|
||||||
|
_bundleLockField = Type.GetFields(_flags).FirstOrDefault(x => x.FieldType == typeof(IBundleLock));
|
||||||
|
_dependencyKeysProperty = Type.GetProperty("DependencyKeys");
|
||||||
|
_keyProperty = Type.GetProperty("Key");
|
||||||
|
_loadStateProperty = Type.GetProperty("LoadState");
|
||||||
|
_loadingCoroutineMethod = Type.GetMethods(_flags).Single(x => x.GetParameters().Length == 0 && x.ReturnType == typeof(Task));
|
||||||
|
}
|
||||||
|
|
||||||
|
public EasyBundleHelper(object easyBundle)
|
||||||
|
{
|
||||||
|
_instance = easyBundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> DependencyKeys
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (IEnumerable<string>)_dependencyKeysProperty.GetValue(_instance);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_dependencyKeysProperty.SetValue(_instance, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBundleLock BundleLock
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (IBundleLock)_bundleLockField.GetValue(_instance);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_bundleLockField.SetValue(_instance, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Path
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (string)_pathField.GetValue(_instance);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_pathField.SetValue(_instance, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Key
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (string)_keyProperty.GetValue(_instance);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_keyProperty.SetValue(_instance, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BindableState LoadState
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (BindableState)_loadStateProperty.GetValue(_instance);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_loadStateProperty.SetValue(_instance, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string KeyWithoutExtension
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (string)_keyWithoutExtensionField.GetValue(_instance);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_keyWithoutExtensionField.SetValue(_instance, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task LoadingCoroutine()
|
||||||
|
{
|
||||||
|
return (Task)_loadingCoroutineMethod.Invoke(_instance, new object[] { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
project/Aki.Debugging/Aki.Debugging.csproj
Normal file
35
project/Aki.Debugging/Aki.Debugging.csproj
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net472</TargetFramework>
|
||||||
|
<AssemblyName>aki-debugging</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Company>Aki</Company>
|
||||||
|
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\Assembly-CSharp.dll" Private="False" />
|
||||||
|
<Reference Include="Unity.TextMeshPro" HintPath="..\Shared\Managed\Unity.TextMeshPro.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.InputLegacyModule" HintPath="..\Shared\Managed\UnityEngine.InputLegacyModule.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.PhysicsModule" HintPath="..\Shared\Managed\UnityEngine.PhysicsModule.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.UI" HintPath="..\Shared\Managed\UnityEngine.UI.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.UIModule" HintPath="..\Shared\Managed\UnityEngine.UIModule.dll" Private="False" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||||
|
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
27
project/Aki.Debugging/AkiDebuggingPlugin.cs
Normal file
27
project/Aki.Debugging/AkiDebuggingPlugin.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using Aki.Debugging.Patches;
|
||||||
|
using BepInEx;
|
||||||
|
|
||||||
|
namespace Aki.Debugging
|
||||||
|
{
|
||||||
|
[BepInPlugin("com.spt-aki.debugging", "AKI.Debugging", "1.0.0")]
|
||||||
|
public class AkiDebuggingPlugin : BaseUnityPlugin
|
||||||
|
{
|
||||||
|
public AkiDebuggingPlugin()
|
||||||
|
{
|
||||||
|
Logger.LogInfo("Loading: Aki.Debugging");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// new CoordinatesPatch().Enable();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"{GetType().Name}: {ex}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInfo("Completed: Aki.Debugging");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
project/Aki.Debugging/Patches/CoordinatesPatch.cs
Normal file
70
project/Aki.Debugging/Patches/CoordinatesPatch.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Debugging.Patches
|
||||||
|
{
|
||||||
|
public class CoordinatesPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static TextMeshProUGUI _alphaLabel;
|
||||||
|
private static PropertyInfo _playerProperty;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var localGameBaseType = PatchConstants.LocalGameType.BaseType;
|
||||||
|
_playerProperty = localGameBaseType.GetProperty("PlayerOwner", BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
return localGameBaseType.GetMethod("Update", PatchConstants.PrivateFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix(object __instance)
|
||||||
|
{
|
||||||
|
if (Input.GetKeyDown(KeyCode.LeftControl))
|
||||||
|
{
|
||||||
|
if (_alphaLabel == null)
|
||||||
|
{
|
||||||
|
_alphaLabel = GameObject.Find("AlphaLabel").GetComponent<TextMeshProUGUI>();
|
||||||
|
_alphaLabel.color = Color.green;
|
||||||
|
_alphaLabel.fontSize = 22;
|
||||||
|
_alphaLabel.font = Resources.Load<TMP_FontAsset>("Fonts & Materials/ARIAL SDF");
|
||||||
|
}
|
||||||
|
|
||||||
|
var playerOwner = (GamePlayerOwner)_playerProperty.GetValue(__instance);
|
||||||
|
var aiming = LookingRaycast(playerOwner.Player);
|
||||||
|
|
||||||
|
if (_alphaLabel != null)
|
||||||
|
{
|
||||||
|
_alphaLabel.text = $"Looking at: [{aiming.x}, {aiming.y}, {aiming.z}]";
|
||||||
|
Logger.LogInfo(_alphaLabel.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
var position = playerOwner.transform.position;
|
||||||
|
var rotation = playerOwner.transform.rotation.eulerAngles;
|
||||||
|
Logger.LogInfo($"Character position: [{position.x},{position.y},{position.z}] | Rotation: [{rotation.x},{rotation.y},{rotation.z}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3 LookingRaycast(Player player)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (player == null || player.Fireport == null)
|
||||||
|
{
|
||||||
|
return Vector3.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
Physics.Linecast(player.Fireport.position, player.Fireport.position - player.Fireport.up * 1000f, out var raycastHit, 331776);
|
||||||
|
return raycastHit.point;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError($"Coordinate Debug raycast failed: {e.Message}");
|
||||||
|
return Vector3.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
project/Aki.PrePatch/Aki.PrePatch.csproj
Normal file
30
project/Aki.PrePatch/Aki.PrePatch.csproj
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net472</TargetFramework>
|
||||||
|
<AssemblyName>aki_PrePatch</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Company>Aki</Company>
|
||||||
|
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\Assembly-CSharp.dll" Private="False" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||||
|
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
31
project/Aki.PrePatch/AkiBotsPrePatcher.cs
Normal file
31
project/Aki.PrePatch/AkiBotsPrePatcher.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Mono.Cecil;
|
||||||
|
|
||||||
|
namespace Aki.PrePatch
|
||||||
|
{
|
||||||
|
public static class AkiBotsPrePatcher
|
||||||
|
{
|
||||||
|
public static IEnumerable<string> TargetDLLs { get; } = new[] { "Assembly-CSharp.dll" };
|
||||||
|
|
||||||
|
public static long sptUsecValue = 0x80000000;
|
||||||
|
public static long sptBearValue = 0x100000000;
|
||||||
|
|
||||||
|
public static void Patch(ref AssemblyDefinition assembly)
|
||||||
|
{
|
||||||
|
var botEnums = assembly.MainModule.GetType("EFT.WildSpawnType");
|
||||||
|
|
||||||
|
var sptUsec = new FieldDefinition("sptUsec",
|
||||||
|
FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault,
|
||||||
|
botEnums)
|
||||||
|
{ Constant = sptUsecValue };
|
||||||
|
|
||||||
|
var sptBear = new FieldDefinition("sptBear",
|
||||||
|
FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault,
|
||||||
|
botEnums)
|
||||||
|
{ Constant = sptBearValue };
|
||||||
|
|
||||||
|
botEnums.Fields.Add(sptUsec);
|
||||||
|
botEnums.Fields.Add(sptBear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
project/Aki.Reflection/Aki.Reflection.csproj
Normal file
35
project/Aki.Reflection/Aki.Reflection.csproj
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net472</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Company>Aki</Company>
|
||||||
|
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\Assembly-CSharp.dll" Private="False" />
|
||||||
|
<Reference Include="bsg.componentace.compression.libs.zlib" HintPath="..\Shared\Managed\bsg.componentace.compression.libs.zlib.dll" Private="False" />
|
||||||
|
<Reference Include="Comfort" HintPath="..\Shared\Managed\Comfort.dll" Private="False" />
|
||||||
|
<Reference Include="FilesChecker" HintPath="..\Shared\Managed\FilesChecker.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="BepInEx.Analyzers" Version="1.0.8" PrivateAssets="All" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
|
||||||
|
<PackageReference Include="BepInEx.Core" Version="5.4.21" />
|
||||||
|
<PackageReference Include="BepInEx.PluginInfoProps" Version="2.1.0" />
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||||
|
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
48
project/Aki.Reflection/CodeWrapper/Code.cs
Normal file
48
project/Aki.Reflection/CodeWrapper/Code.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
|
||||||
|
namespace Aki.Reflection.CodeWrapper
|
||||||
|
{
|
||||||
|
public class Code
|
||||||
|
{
|
||||||
|
public OpCode OpCode { get; }
|
||||||
|
public Type CallerType { get; }
|
||||||
|
public object OperandTarget { get; }
|
||||||
|
public Type[] Parameters { get; }
|
||||||
|
public bool HasOperand { get; }
|
||||||
|
|
||||||
|
public Code(OpCode opCode)
|
||||||
|
{
|
||||||
|
OpCode = opCode;
|
||||||
|
HasOperand = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Code(OpCode opCode, object operandTarget)
|
||||||
|
{
|
||||||
|
OpCode = opCode;
|
||||||
|
OperandTarget = operandTarget;
|
||||||
|
HasOperand = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Code(OpCode opCode, Type callerType)
|
||||||
|
{
|
||||||
|
OpCode = opCode;
|
||||||
|
CallerType = callerType;
|
||||||
|
HasOperand = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Code(OpCode opCode, Type callerType, object operandTarget, Type[] parameters = null)
|
||||||
|
{
|
||||||
|
OpCode = opCode;
|
||||||
|
CallerType = callerType;
|
||||||
|
OperandTarget = operandTarget;
|
||||||
|
Parameters = parameters;
|
||||||
|
HasOperand = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Label? GetLabel()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
project/Aki.Reflection/CodeWrapper/CodeGenerator.cs
Normal file
66
project/Aki.Reflection/CodeWrapper/CodeGenerator.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace Aki.Reflection.CodeWrapper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class to generate IL code for transpilers
|
||||||
|
/// </summary>
|
||||||
|
public class CodeGenerator
|
||||||
|
{
|
||||||
|
public static List<CodeInstruction> GenerateInstructions(List<Code> codes)
|
||||||
|
{
|
||||||
|
var list = new List<CodeInstruction>();
|
||||||
|
|
||||||
|
foreach (Code code in codes)
|
||||||
|
{
|
||||||
|
list.Add(ParseCode(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CodeInstruction ParseCode(Code code)
|
||||||
|
{
|
||||||
|
if (!code.HasOperand)
|
||||||
|
{
|
||||||
|
return new CodeInstruction(code.OpCode) { labels = GetLabelList(code) };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code.OpCode == OpCodes.Ldfld || code.OpCode == OpCodes.Stfld)
|
||||||
|
{
|
||||||
|
return new CodeInstruction(code.OpCode, AccessTools.Field(code.CallerType, code.OperandTarget as string)) { labels = GetLabelList(code) };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code.OpCode == OpCodes.Call || code.OpCode == OpCodes.Callvirt)
|
||||||
|
{
|
||||||
|
return new CodeInstruction(code.OpCode, AccessTools.Method(code.CallerType, code.OperandTarget as string, code.Parameters)) { labels = GetLabelList(code) };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code.OpCode == OpCodes.Box)
|
||||||
|
{
|
||||||
|
return new CodeInstruction(code.OpCode, code.CallerType) { labels = GetLabelList(code) };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code.OpCode == OpCodes.Br || code.OpCode == OpCodes.Brfalse || code.OpCode == OpCodes.Brtrue || code.OpCode == OpCodes.Brtrue_S
|
||||||
|
|| code.OpCode == OpCodes.Brfalse_S || code.OpCode == OpCodes.Br_S)
|
||||||
|
{
|
||||||
|
return new CodeInstruction(code.OpCode, code.OperandTarget) { labels = GetLabelList(code) };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException($"Code with OpCode {nameof(code.OpCode)} is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Label> GetLabelList(Code code)
|
||||||
|
{
|
||||||
|
if (code.GetLabel() == null)
|
||||||
|
{
|
||||||
|
return new List<Label>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new List<Label>() { (Label)code.GetLabel() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
project/Aki.Reflection/CodeWrapper/CodeWithLabel.cs
Normal file
35
project/Aki.Reflection/CodeWrapper/CodeWithLabel.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
|
||||||
|
namespace Aki.Reflection.CodeWrapper
|
||||||
|
{
|
||||||
|
public class CodeWithLabel : Code
|
||||||
|
{
|
||||||
|
public Label Label { get; }
|
||||||
|
|
||||||
|
public CodeWithLabel(OpCode opCode, Label label) : base(opCode)
|
||||||
|
{
|
||||||
|
Label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWithLabel(OpCode opCode, Label label, object operandTarget) : base(opCode, operandTarget)
|
||||||
|
{
|
||||||
|
Label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWithLabel(OpCode opCode, Label label, Type callerType) : base(opCode, callerType)
|
||||||
|
{
|
||||||
|
Label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWithLabel(OpCode opCode, Label label, Type callerType, object operandTarget, Type[] parameters = null) : base(opCode, callerType, operandTarget, parameters)
|
||||||
|
{
|
||||||
|
Label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Label? GetLabel()
|
||||||
|
{
|
||||||
|
return Label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
project/Aki.Reflection/Patching/Attributes.cs
Normal file
35
project/Aki.Reflection/Patching/Attributes.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace Aki.Reflection.Patching
|
||||||
|
{
|
||||||
|
[MeansImplicitUse]
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class PatchPrefixAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MeansImplicitUse]
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class PatchPostfixAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MeansImplicitUse]
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class PatchTranspilerAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MeansImplicitUse]
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class PatchFinalizerAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MeansImplicitUse]
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class PatchILManipulatorAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
150
project/Aki.Reflection/Patching/ModulePatch.cs
Normal file
150
project/Aki.Reflection/Patching/ModulePatch.cs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using BepInEx.Logging;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace Aki.Reflection.Patching
|
||||||
|
{
|
||||||
|
public abstract class ModulePatch
|
||||||
|
{
|
||||||
|
private readonly Harmony _harmony;
|
||||||
|
private readonly List<HarmonyMethod> _prefixList;
|
||||||
|
private readonly List<HarmonyMethod> _postfixList;
|
||||||
|
private readonly List<HarmonyMethod> _transpilerList;
|
||||||
|
private readonly List<HarmonyMethod> _finalizerList;
|
||||||
|
private readonly List<HarmonyMethod> _ilmanipulatorList;
|
||||||
|
|
||||||
|
protected static ManualLogSource Logger { get; private set; }
|
||||||
|
|
||||||
|
protected ModulePatch() : this(null)
|
||||||
|
{
|
||||||
|
if (Logger == null)
|
||||||
|
{
|
||||||
|
Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(ModulePatch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Name</param>
|
||||||
|
protected ModulePatch(string name = null)
|
||||||
|
{
|
||||||
|
_harmony = new Harmony(name ?? GetType().Name);
|
||||||
|
_prefixList = GetPatchMethods(typeof(PatchPrefixAttribute));
|
||||||
|
_postfixList = GetPatchMethods(typeof(PatchPostfixAttribute));
|
||||||
|
_transpilerList = GetPatchMethods(typeof(PatchTranspilerAttribute));
|
||||||
|
_finalizerList = GetPatchMethods(typeof(PatchFinalizerAttribute));
|
||||||
|
_ilmanipulatorList = GetPatchMethods(typeof(PatchILManipulatorAttribute));
|
||||||
|
|
||||||
|
if (_prefixList.Count == 0
|
||||||
|
&& _postfixList.Count == 0
|
||||||
|
&& _transpilerList.Count == 0
|
||||||
|
&& _finalizerList.Count == 0
|
||||||
|
&& _ilmanipulatorList.Count == 0)
|
||||||
|
{
|
||||||
|
throw new Exception($"{_harmony.Id}: At least one of the patch methods must be specified");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get original method
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Method</returns>
|
||||||
|
protected abstract MethodBase GetTargetMethod();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get HarmonyMethod from string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="attributeType">Attribute type</param>
|
||||||
|
/// <returns>Method</returns>
|
||||||
|
private List<HarmonyMethod> GetPatchMethods(Type attributeType)
|
||||||
|
{
|
||||||
|
var T = GetType();
|
||||||
|
var methods = new List<HarmonyMethod>();
|
||||||
|
|
||||||
|
foreach (var method in T.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly))
|
||||||
|
{
|
||||||
|
if (method.GetCustomAttribute(attributeType) != null)
|
||||||
|
{
|
||||||
|
methods.Add(new HarmonyMethod(method));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply patch to target
|
||||||
|
/// </summary>
|
||||||
|
public void Enable()
|
||||||
|
{
|
||||||
|
var target = GetTargetMethod();
|
||||||
|
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{_harmony.Id}: TargetMethod is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var prefix in _prefixList)
|
||||||
|
{
|
||||||
|
_harmony.Patch(target, prefix: prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var postfix in _postfixList)
|
||||||
|
{
|
||||||
|
_harmony.Patch(target, postfix: postfix);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var transpiler in _transpilerList)
|
||||||
|
{
|
||||||
|
_harmony.Patch(target, transpiler: transpiler);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var finalizer in _finalizerList)
|
||||||
|
{
|
||||||
|
_harmony.Patch(target, finalizer: finalizer);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var ilmanipulator in _ilmanipulatorList)
|
||||||
|
{
|
||||||
|
_harmony.Patch(target, ilmanipulator: ilmanipulator);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInfo($"Enabled patch {_harmony.Id}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"{_harmony.Id}: {ex}");
|
||||||
|
throw new Exception($"{_harmony.Id}:", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove applied patch from target
|
||||||
|
/// </summary>
|
||||||
|
public void Disable()
|
||||||
|
{
|
||||||
|
var target = GetTargetMethod();
|
||||||
|
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{_harmony.Id}: TargetMethod is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_harmony.Unpatch(target, HarmonyPatchType.All, _harmony.Id);
|
||||||
|
Logger.LogInfo($"Disabled patch {_harmony.Id}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"{_harmony.Id}: {ex}");
|
||||||
|
throw new Exception($"{_harmony.Id}:", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
project/Aki.Reflection/Utils/ClientAppUtils.cs
Normal file
18
project/Aki.Reflection/Utils/ClientAppUtils.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
|
||||||
|
namespace Aki.Reflection.Utils
|
||||||
|
{
|
||||||
|
public static class ClientAppUtils
|
||||||
|
{
|
||||||
|
public static ClientApplication<ISession> GetClientApp()
|
||||||
|
{
|
||||||
|
return Singleton<ClientApplication<ISession>>.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TarkovApplication GetMainApp()
|
||||||
|
{
|
||||||
|
return GetClientApp() as TarkovApplication;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
project/Aki.Reflection/Utils/HookObject.cs
Normal file
28
project/Aki.Reflection/Utils/HookObject.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.Reflection.Utils
|
||||||
|
{
|
||||||
|
public static class HookObject
|
||||||
|
{
|
||||||
|
public static GameObject _object
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
GameObject result = GameObject.Find("Aki.Hook");
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
result = new GameObject("Aki.Hook");
|
||||||
|
Object.DontDestroyOnLoad(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T AddOrGetComponent<T>() where T : MonoBehaviour
|
||||||
|
{
|
||||||
|
return _object.GetOrAddComponent<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
project/Aki.Reflection/Utils/PatchConstants.cs
Normal file
49
project/Aki.Reflection/Utils/PatchConstants.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using FilesChecker;
|
||||||
|
|
||||||
|
namespace Aki.Reflection.Utils
|
||||||
|
{
|
||||||
|
public static class PatchConstants
|
||||||
|
{
|
||||||
|
public static BindingFlags PrivateFlags { get; private set; }
|
||||||
|
public static BindingFlags PublicFlags { get; private set; }
|
||||||
|
public static Type[] EftTypes { get; private set; }
|
||||||
|
public static Type[] FilesCheckerTypes { get; private set; }
|
||||||
|
public static Type LocalGameType { get; private set; }
|
||||||
|
public static Type ExfilPointManagerType { get; private set; }
|
||||||
|
public static Type SessionInterfaceType { get; private set; }
|
||||||
|
public static Type BackendSessionInterfaceType { get; private set; }
|
||||||
|
|
||||||
|
private static ISession _backEndSession;
|
||||||
|
public static ISession BackEndSession
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_backEndSession == null)
|
||||||
|
{
|
||||||
|
_backEndSession = Singleton<ClientApplication<ISession>>.Instance.GetClientBackEndSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _backEndSession;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PatchConstants()
|
||||||
|
{
|
||||||
|
_ = nameof(ISession.GetPhpSessionId);
|
||||||
|
|
||||||
|
PrivateFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
|
||||||
|
PublicFlags = BindingFlags.Public | BindingFlags.Instance;
|
||||||
|
EftTypes = typeof(AbstractGame).Assembly.GetTypes();
|
||||||
|
FilesCheckerTypes = typeof(ICheckResult).Assembly.GetTypes();
|
||||||
|
LocalGameType = EftTypes.Single(x => x.Name == "LocalGame");
|
||||||
|
ExfilPointManagerType = EftTypes.Single(x => x.GetMethod("InitAllExfiltrationPoints") != null);
|
||||||
|
SessionInterfaceType = EftTypes.Single(x => x.GetMethods().Select(y => y.Name).Contains("GetPhpSessionId") && x.IsInterface);
|
||||||
|
BackendSessionInterfaceType = EftTypes.Single(x => x.GetMethods().Select(y => y.Name).Contains("ChangeProfileStatus") && x.IsInterface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
project/Aki.SinglePlayer/Aki.SinglePlayer.csproj
Normal file
35
project/Aki.SinglePlayer/Aki.SinglePlayer.csproj
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net472</TargetFramework>
|
||||||
|
<AssemblyName>aki-singleplayer</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Company>Aki</Company>
|
||||||
|
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\Assembly-CSharp.dll" Private="False" />
|
||||||
|
<Reference Include="Comfort.Unity" HintPath="..\Shared\Managed\Comfort.Unity.dll" Private="False" />
|
||||||
|
<Reference Include="DissonanceVoip" HintPath="..\Shared\Managed\DissonanceVoip.dll" Private="False" />
|
||||||
|
<Reference Include="ItemComponent.Types" HintPath="..\Shared\Managed\ItemComponent.Types.dll" Private="False" />
|
||||||
|
<Reference Include="Comfort" HintPath="..\Shared\Managed\Comfort.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
||||||
|
<Reference Include="UnityEngine.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||||
|
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
59
project/Aki.SinglePlayer/AkiSingleplayerPlugin.cs
Normal file
59
project/Aki.SinglePlayer/AkiSingleplayerPlugin.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using Aki.SinglePlayer.Patches.Healing;
|
||||||
|
using Aki.SinglePlayer.Patches.MainMenu;
|
||||||
|
using Aki.SinglePlayer.Patches.Progression;
|
||||||
|
using Aki.SinglePlayer.Patches.Quests;
|
||||||
|
using Aki.SinglePlayer.Patches.RaidFix;
|
||||||
|
using Aki.SinglePlayer.Patches.ScavMode;
|
||||||
|
using BepInEx;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer
|
||||||
|
{
|
||||||
|
[BepInPlugin("com.spt-aki.singleplayer", "AKI.Singleplayer", "1.0.0")]
|
||||||
|
class AkiSingleplayerPlugin : BaseUnityPlugin
|
||||||
|
{
|
||||||
|
public AkiSingleplayerPlugin()
|
||||||
|
{
|
||||||
|
Logger.LogInfo("Loading: Aki.SinglePlayer");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
new OfflineSaveProfilePatch().Enable();
|
||||||
|
new OfflineSpawnPointPatch().Enable();
|
||||||
|
new ExperienceGainPatch().Enable();
|
||||||
|
new MainMenuControllerPatch().Enable();
|
||||||
|
new PlayerPatch().Enable();
|
||||||
|
new SelectLocationScreenPatch().Enable();
|
||||||
|
new InsuranceScreenPatch().Enable();
|
||||||
|
new BotTemplateLimitPatch().Enable();
|
||||||
|
new GetNewBotTemplatesPatch().Enable();
|
||||||
|
new RemoveUsedBotProfilePatch().Enable();
|
||||||
|
new DogtagPatch().Enable();
|
||||||
|
new LoadOfflineRaidScreenPatch().Enable();
|
||||||
|
new ScavPrefabLoadPatch().Enable();
|
||||||
|
new ScavProfileLoadPatch().Enable();
|
||||||
|
new ScavExfilPatch().Enable();
|
||||||
|
new ExfilPointManagerPatch().Enable();
|
||||||
|
new TinnitusFixPatch().Enable();
|
||||||
|
new MaxBotPatch().Enable();
|
||||||
|
new SpawnPmcPatch().Enable();
|
||||||
|
new PostRaidHealingPricePatch().Enable();
|
||||||
|
new EndByTimerPatch().Enable();
|
||||||
|
new PostRaidHealScreenPatch().Enable();
|
||||||
|
new VoIPTogglerPatch().Enable();
|
||||||
|
new MidRaidQuestChangePatch().Enable();
|
||||||
|
new HealthControllerPatch().Enable();
|
||||||
|
new LighthouseBridgePatch().Enable();
|
||||||
|
new LighthouseTransmitterPatch().Enable();
|
||||||
|
new EmptyInfilFixPatch().Enable();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"{GetType().Name}: {ex}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInfo("Completed: Aki.SinglePlayer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
project/Aki.SinglePlayer/Models/Healing/BodyPartHealth.cs
Normal file
48
project/Aki.SinglePlayer/Models/Healing/BodyPartHealth.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Models.Healing
|
||||||
|
{
|
||||||
|
public class BodyPartHealth
|
||||||
|
{
|
||||||
|
private readonly Dictionary<EBodyPartEffect, float> _effects = new Dictionary<EBodyPartEffect, float>();
|
||||||
|
public float Maximum { get; private set; }
|
||||||
|
public float Current { get; private set; }
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<EBodyPartEffect, float> Effects => _effects;
|
||||||
|
|
||||||
|
public void Initialize(float current, float maximum)
|
||||||
|
{
|
||||||
|
Maximum = maximum;
|
||||||
|
Current = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeHealth(float diff)
|
||||||
|
{
|
||||||
|
Current += diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddEffect(EBodyPartEffect bodyPartEffect, float time = -1)
|
||||||
|
{
|
||||||
|
_effects[bodyPartEffect] = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateEffect(EBodyPartEffect bodyPartEffect, float time)
|
||||||
|
{
|
||||||
|
_effects[bodyPartEffect] = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void RemoveAllEffects()
|
||||||
|
{
|
||||||
|
_effects.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveEffect(EBodyPartEffect bodyPartEffect)
|
||||||
|
{
|
||||||
|
if (_effects.ContainsKey(bodyPartEffect))
|
||||||
|
{
|
||||||
|
_effects.Remove(bodyPartEffect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
project/Aki.SinglePlayer/Models/Healing/EBodyPartEffect.cs
Normal file
12
project/Aki.SinglePlayer/Models/Healing/EBodyPartEffect.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace Aki.SinglePlayer.Models.Healing
|
||||||
|
{
|
||||||
|
public enum EBodyPartEffect
|
||||||
|
{
|
||||||
|
Fracture,
|
||||||
|
LightBleeding,
|
||||||
|
HeavyBleeding,
|
||||||
|
MildMusclePain,
|
||||||
|
SevereMusclePain,
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
}
|
29
project/Aki.SinglePlayer/Models/Healing/PlayerHealth.cs
Normal file
29
project/Aki.SinglePlayer/Models/Healing/PlayerHealth.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Models.Healing
|
||||||
|
{
|
||||||
|
public class PlayerHealth
|
||||||
|
{
|
||||||
|
private readonly Dictionary<EBodyPart, BodyPartHealth> _health;
|
||||||
|
public IReadOnlyDictionary<EBodyPart, BodyPartHealth> Health => _health;
|
||||||
|
public bool IsAlive { get; set; }
|
||||||
|
public float Hydration { get; set; }
|
||||||
|
public float Energy { get; set; }
|
||||||
|
public float Temperature { get; set; }
|
||||||
|
|
||||||
|
public PlayerHealth()
|
||||||
|
{
|
||||||
|
IsAlive = true;
|
||||||
|
_health = new Dictionary<EBodyPart, BodyPartHealth>()
|
||||||
|
{
|
||||||
|
{ EBodyPart.Head, new BodyPartHealth() },
|
||||||
|
{ EBodyPart.Chest, new BodyPartHealth() },
|
||||||
|
{ EBodyPart.Stomach, new BodyPartHealth() },
|
||||||
|
{ EBodyPart.LeftArm, new BodyPartHealth() },
|
||||||
|
{ EBodyPart.RightArm, new BodyPartHealth() },
|
||||||
|
{ EBodyPart.LeftLeg, new BodyPartHealth() },
|
||||||
|
{ EBodyPart.RightLeg, new BodyPartHealth() }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,144 @@
|
|||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Models.Progression
|
||||||
|
{
|
||||||
|
public class LighthouseProgressionClass : MonoBehaviour
|
||||||
|
{
|
||||||
|
private bool _isScav;
|
||||||
|
private GameWorld _gameWorld;
|
||||||
|
private float _timer;
|
||||||
|
private bool _addedToEnemy;
|
||||||
|
private List<MineDirectionalColliders> _mines;
|
||||||
|
private RecodableItemClass _transmitter;
|
||||||
|
private List<Player> _bosses;
|
||||||
|
private bool _aggressor;
|
||||||
|
private bool _disabledDoor;
|
||||||
|
private readonly string _transmitterId = "62e910aaf957f2915e0a5e36";
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
_gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
_bosses = new List<Player>();
|
||||||
|
_mines = GameObject.FindObjectsOfType<MineDirectionalColliders>().ToList();
|
||||||
|
|
||||||
|
if (_gameWorld == null || _gameWorld.MainPlayer.Location.ToLower() != "lighthouse") return;
|
||||||
|
|
||||||
|
// if player is a scav, there is no need to continue this method.
|
||||||
|
if (_gameWorld.MainPlayer.Side == EPlayerSide.Savage)
|
||||||
|
{
|
||||||
|
_isScav = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the players Transmitter.
|
||||||
|
_transmitter = (RecodableItemClass) _gameWorld.MainPlayer.Profile.Inventory.AllRealPlayerItems.FirstOrDefault(x => x.TemplateId == _transmitterId);
|
||||||
|
|
||||||
|
if (_transmitter != null)
|
||||||
|
{
|
||||||
|
GameObject.Find("Attack").SetActive(false);
|
||||||
|
|
||||||
|
// this zone was added in a newer version and the gameObject actually has a \
|
||||||
|
GameObject.Find("CloseZone\\").SetActive(false);
|
||||||
|
|
||||||
|
// Give access to the Lightkeepers door.
|
||||||
|
_gameWorld.BufferZoneController.SetPlayerAccessStatus(_gameWorld.MainPlayer.ProfileId, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (_gameWorld == null || _addedToEnemy || _disabledDoor || _transmitter == null) return;
|
||||||
|
|
||||||
|
_timer += Time.deltaTime;
|
||||||
|
|
||||||
|
if (_timer < 10f) return;
|
||||||
|
|
||||||
|
if (_bosses.Count == 0)
|
||||||
|
{
|
||||||
|
SetupBosses();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isScav)
|
||||||
|
{
|
||||||
|
PlayerIsScav();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_gameWorld?.MainPlayer?.HandsController?.Item?.TemplateId == _transmitterId)
|
||||||
|
{
|
||||||
|
if (_transmitter?.RecodableComponent?.Status == RadioTransmitterStatus.Green)
|
||||||
|
{
|
||||||
|
foreach (var mine in _mines)
|
||||||
|
{
|
||||||
|
if (mine.gameObject.activeSelf)
|
||||||
|
{
|
||||||
|
mine.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var mine in _mines)
|
||||||
|
{
|
||||||
|
if (!mine.gameObject.activeSelf)
|
||||||
|
{
|
||||||
|
mine.gameObject.SetActive(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_aggressor)
|
||||||
|
{
|
||||||
|
PlayerIsAggressor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupBosses()
|
||||||
|
{
|
||||||
|
foreach (var player in _gameWorld.AllPlayers)
|
||||||
|
{
|
||||||
|
if (!player.IsYourPlayer)
|
||||||
|
{
|
||||||
|
if (player.AIData.BotOwner.IsRole(WildSpawnType.bossZryachiy) || player.AIData.BotOwner.IsRole(WildSpawnType.followerZryachiy))
|
||||||
|
{
|
||||||
|
// Sub to Bosses OnDeath event, Set mainplayer to aggressor on this script
|
||||||
|
player.OnPlayerDeadOrUnspawn += player1 =>
|
||||||
|
{
|
||||||
|
if (player1.KillerId != null && player1.KillerId == _gameWorld.MainPlayer.ProfileId)
|
||||||
|
{
|
||||||
|
_aggressor = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_bosses.Add(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayerIsScav()
|
||||||
|
{
|
||||||
|
// If player is a scav, they must be added to the bosses enemy list otherwise they wont kill them
|
||||||
|
foreach (var boss in _bosses)
|
||||||
|
{
|
||||||
|
boss.AIData.BotOwner.BotsGroup.AddEnemy(_gameWorld.MainPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_addedToEnemy = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayerIsAggressor()
|
||||||
|
{
|
||||||
|
// Disable access to Lightkeepers door for the player
|
||||||
|
_gameWorld.BufferZoneController.SetPlayerAccessStatus(_gameWorld.MainPlayer.ProfileId, false);
|
||||||
|
_transmitter?.RecodableComponent?.SetStatus(RadioTransmitterStatus.Yellow);
|
||||||
|
_transmitter?.RecodableComponent?.SetEncoded(false);
|
||||||
|
_disabledDoor = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
using Aki.SinglePlayer.Models.Healing;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using EFT;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Models.Progression
|
||||||
|
{
|
||||||
|
public class SaveProfileRequest
|
||||||
|
{
|
||||||
|
[JsonProperty("exit")]
|
||||||
|
public string Exit;
|
||||||
|
|
||||||
|
[JsonProperty("profile")]
|
||||||
|
public Profile Profile;
|
||||||
|
|
||||||
|
[JsonProperty("isPlayerScav")]
|
||||||
|
public bool IsPlayerScav;
|
||||||
|
|
||||||
|
[JsonProperty("health")]
|
||||||
|
public PlayerHealth Health;
|
||||||
|
|
||||||
|
public SaveProfileRequest()
|
||||||
|
{
|
||||||
|
Exit = "left";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
project/Aki.SinglePlayer/Models/RaidFix/BundleLoader.cs
Normal file
40
project/Aki.SinglePlayer/Models/RaidFix/BundleLoader.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Models.RaidFix
|
||||||
|
{
|
||||||
|
public struct BundleLoader
|
||||||
|
{
|
||||||
|
Profile Profile;
|
||||||
|
TaskScheduler TaskScheduler { get; }
|
||||||
|
|
||||||
|
public BundleLoader(TaskScheduler taskScheduler)
|
||||||
|
{
|
||||||
|
Profile = null;
|
||||||
|
TaskScheduler = taskScheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Profile> LoadBundles(Task<Profile> task)
|
||||||
|
{
|
||||||
|
Profile = task.Result;
|
||||||
|
|
||||||
|
var loadTask = Singleton<PoolManager>.Instance.LoadBundlesAndCreatePools(
|
||||||
|
PoolManager.PoolsCategory.Raid,
|
||||||
|
PoolManager.AssemblyType.Local,
|
||||||
|
Profile.GetAllPrefabPaths(false).ToArray(),
|
||||||
|
JobPriority.General,
|
||||||
|
null,
|
||||||
|
default(CancellationToken));
|
||||||
|
|
||||||
|
return loadTask.ContinueWith(GetProfile, TaskScheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Profile GetProfile(Task task)
|
||||||
|
{
|
||||||
|
return Profile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Healing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// HealthController used by post-raid heal screen and health listenen class are different, this patch
|
||||||
|
/// ensures effects (fracture/bleeding) on body parts stay in sync
|
||||||
|
/// </summary>
|
||||||
|
public class HealthControllerPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(HealthControllerClass).GetMethod("ApplyTreatment", BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix(object healthObserver)
|
||||||
|
{
|
||||||
|
var property = healthObserver.GetType().GetProperty("Effects");
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
var effects = property.GetValue(healthObserver);
|
||||||
|
if (effects != null && effects.GetType().GetGenericTypeDefinition() == typeof(List<>))
|
||||||
|
{
|
||||||
|
var parsedEffects = ((IList)effects).Cast<object>().ToList();
|
||||||
|
parsedEffects.ForEach(effect =>
|
||||||
|
{
|
||||||
|
var parsedEffect = effect as IEffect;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// I tried using reflections to raise the event, I also tried replacing the effect
|
||||||
|
// health controller using reflections but they are different types than the one the actual
|
||||||
|
// player class uses so it cant be replaced.
|
||||||
|
// Unfortunately, the easiest way to deal with this is just manually triggering the HealthListener method
|
||||||
|
Utils.Healing.HealthListener.Instance.OnEffectRemovedEvent(parsedEffect);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"Exception!\n{ex.Message}\n{ex.StackTrace}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogDebug("No effects found to heal on the observer!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Healing
|
||||||
|
{
|
||||||
|
public class MainMenuControllerPatch : ModulePatch
|
||||||
|
{
|
||||||
|
static MainMenuControllerPatch()
|
||||||
|
{
|
||||||
|
_ = nameof(IHealthController.HydrationChangedEvent);
|
||||||
|
_ = nameof(MainMenuController.HealthController);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var desiredType = typeof(MainMenuController);
|
||||||
|
var desiredMethod = desiredType.GetMethod("method_1", PatchConstants.PrivateFlags);
|
||||||
|
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(MainMenuController __instance)
|
||||||
|
{
|
||||||
|
var healthController = __instance.HealthController;
|
||||||
|
var listener = Utils.Healing.HealthListener.Instance;
|
||||||
|
|
||||||
|
if (healthController == null)
|
||||||
|
{
|
||||||
|
Logger.LogInfo("MainMenuControllerPatch() - healthController is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listener == null)
|
||||||
|
{
|
||||||
|
Logger.LogInfo("MainMenuControllerPatch() - listener is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (healthController != null && listener != null)
|
||||||
|
{
|
||||||
|
listener.Init(healthController, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
project/Aki.SinglePlayer/Patches/Healing/PlayerPatch.cs
Normal file
40
project/Aki.SinglePlayer/Patches/Healing/PlayerPatch.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Healing
|
||||||
|
{
|
||||||
|
public class PlayerPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var desiredType = typeof(Player);
|
||||||
|
var desiredMethod = desiredType.GetMethod("Init", PatchConstants.PrivateFlags);
|
||||||
|
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static async void PatchPostfix(Task __result, Player __instance, Profile profile)
|
||||||
|
{
|
||||||
|
await __result;
|
||||||
|
|
||||||
|
if (profile?.Id.StartsWith("pmc") == true)
|
||||||
|
{
|
||||||
|
Logger.LogDebug($"Hooking up health listener to profile: {profile.Id}");
|
||||||
|
var listener = Utils.Healing.HealthListener.Instance;
|
||||||
|
listener.Init(__instance.HealthController, true);
|
||||||
|
Logger.LogDebug($"HealthController instance: {__instance.HealthController.GetHashCode()}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogDebug($"Skipped on HealthController instance: {__instance.HealthController.GetHashCode()} for profile id: {profile?.Id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Healing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// We need to alter Class1049.smethod_0().
|
||||||
|
/// Set the passed in ERaidMode to online, this ensures the heal screen shows.
|
||||||
|
/// It cannot be changed in the calling method as doing so causes the post-raid exp display to remain at 0
|
||||||
|
/// </summary>
|
||||||
|
public class PostRaidHealScreenPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
// Class1049.smethod_0 as of 18969
|
||||||
|
//internal static Class1049 smethod_0(GInterface29 backend, string profileId, Profile savageProfile, LocationSettingsClass.GClass1097 location, ExitStatus exitStatus, TimeSpan exitTime, ERaidMode raidMode)
|
||||||
|
var desiredType = PatchConstants.EftTypes.Single(x => x.Name == "PostRaidHealthScreenClass");
|
||||||
|
var desiredMethod = desiredType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic).Single(IsTargetMethod);
|
||||||
|
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsTargetMethod(MethodInfo mi)
|
||||||
|
{
|
||||||
|
var parameters = mi.GetParameters();
|
||||||
|
return parameters.Length == 7
|
||||||
|
&& parameters[0].Name == "session"
|
||||||
|
&& parameters[1].Name == "profileId"
|
||||||
|
&& parameters[2].Name == "savageProfile"
|
||||||
|
&& parameters[3].Name == "location"
|
||||||
|
&& parameters[4].Name == "exitStatus"
|
||||||
|
&& parameters[5].Name == "exitTime"
|
||||||
|
&& parameters[6].Name == "raidMode";
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(TarkovApplication __instance, ref ERaidMode raidMode)
|
||||||
|
{
|
||||||
|
raidMode = ERaidMode.Online;
|
||||||
|
|
||||||
|
return true; // Perform original method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.MainMenu
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Force ERaidMode to online to make interface show insurance page
|
||||||
|
/// </summary>
|
||||||
|
class InsuranceScreenPatch : ModulePatch
|
||||||
|
{
|
||||||
|
static InsuranceScreenPatch()
|
||||||
|
{
|
||||||
|
_ = nameof(MainMenuController.InventoryController);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var desiredType = typeof(MainMenuController);
|
||||||
|
var desiredMethod = desiredType.GetMethod("method_66", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PrefixPatch(RaidSettings ___raidSettings_0)
|
||||||
|
{
|
||||||
|
___raidSettings_0.RaidMode = ERaidMode.Online;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PostfixPatch(RaidSettings ___raidSettings_0)
|
||||||
|
{
|
||||||
|
___raidSettings_0.RaidMode = ERaidMode.Local;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT.UI;
|
||||||
|
using EFT.UI.Matchmaker;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.MainMenu
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Remove the ready button from select location screen
|
||||||
|
/// </summary>
|
||||||
|
public class SelectLocationScreenPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var desiredType = typeof(MatchMakerSelectionLocationScreen);
|
||||||
|
var desiredMethod = desiredType.GetMethod("Awake", PatchConstants.PrivateFlags);
|
||||||
|
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(DefaultUIButton ____readyButton)
|
||||||
|
{
|
||||||
|
____readyButton.Interactable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT.UI.SessionEnd;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Progression
|
||||||
|
{
|
||||||
|
public class ExperienceGainPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var desiredType = typeof(SessionResultExperienceCount);
|
||||||
|
var desiredMethod = desiredType.GetMethods(PatchConstants.PrivateFlags).FirstOrDefault(IsTargetMethod);
|
||||||
|
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsTargetMethod(MethodInfo mi)
|
||||||
|
{
|
||||||
|
var parameters = mi.GetParameters();
|
||||||
|
return (parameters.Length == 3
|
||||||
|
&& parameters[0].Name == "profile"
|
||||||
|
&& parameters[1].Name == "isOnline"
|
||||||
|
&& parameters[2].Name == "exitStatus"
|
||||||
|
&& parameters[1].ParameterType == typeof(bool));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix(ref bool isOnline)
|
||||||
|
{
|
||||||
|
isOnline = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(ref bool isOnline)
|
||||||
|
{
|
||||||
|
isOnline = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.SinglePlayer.Models.Progression;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Progression
|
||||||
|
{
|
||||||
|
public class LighthouseBridgePatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(GameWorld).GetMethod(nameof(GameWorld.OnGameStarted));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix()
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
|
||||||
|
if (gameWorld == null || gameWorld.MainPlayer.Location.ToLower() != "lighthouse") return;
|
||||||
|
|
||||||
|
gameWorld.GetOrAddComponent<LighthouseProgressionClass>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Progression
|
||||||
|
{
|
||||||
|
public class LighthouseTransmitterPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(RadioTransmitterHandlerClass).GetMethod("method_4", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(RadioTransmitterHandlerClass __instance)
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
|
||||||
|
if (gameWorld == null) return false;
|
||||||
|
|
||||||
|
var transmitter = __instance.RecodableComponent;
|
||||||
|
|
||||||
|
if (transmitter.IsEncoded)
|
||||||
|
{
|
||||||
|
transmitter.SetStatus(RadioTransmitterStatus.Green);
|
||||||
|
}
|
||||||
|
else if (gameWorld.MainPlayer.IsAgressorInLighthouseTraderZone)
|
||||||
|
{
|
||||||
|
// this might need to be tested and changed as I don't think this currently is affect upon killing bosses
|
||||||
|
transmitter.SetStatus(RadioTransmitterStatus.Yellow);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transmitter.SetStatus(RadioTransmitterStatus.Red);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Progression
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// After picking up a quest item, trigger CheckForStatusChange() from the questController to fully update a quest subtasks to show (e.g. `survive and extract item from raid` task)
|
||||||
|
/// </summary>
|
||||||
|
public class MidRaidQuestChangePatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(Profile).GetMethod("AddToCarriedQuestItems", BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix()
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
|
||||||
|
if (gameWorld != null)
|
||||||
|
{
|
||||||
|
var player = gameWorld.MainPlayer;
|
||||||
|
|
||||||
|
var questController = Traverse.Create(player).Field<QuestControllerClass>("_questController").Value;
|
||||||
|
if (questController != null)
|
||||||
|
{
|
||||||
|
foreach (var quest in questController.Quests.ToList())
|
||||||
|
{
|
||||||
|
quest.CheckForStatusChange(true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using Aki.SinglePlayer.Models.Progression;
|
||||||
|
using Aki.SinglePlayer.Utils.Progression;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Progression
|
||||||
|
{
|
||||||
|
public class OfflineSaveProfilePatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static readonly JsonConverter[] _defaultJsonConverters;
|
||||||
|
|
||||||
|
static OfflineSaveProfilePatch()
|
||||||
|
{
|
||||||
|
_ = nameof(ClientMetrics.Metrics);
|
||||||
|
|
||||||
|
var converterClass = typeof(AbstractGame).Assembly.GetTypes()
|
||||||
|
.First(t => t.GetField("Converters", BindingFlags.Static | BindingFlags.Public) != null);
|
||||||
|
|
||||||
|
_defaultJsonConverters = Traverse.Create(converterClass).Field<JsonConverter[]>("Converters").Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
// method_45 - as of 16432
|
||||||
|
// method_43 - as of 18876
|
||||||
|
var desiredType = PatchConstants.EftTypes.Single(x => x.Name == "TarkovApplication");
|
||||||
|
var desiredMethod = Array.Find(desiredType.GetMethods(PatchConstants.PrivateFlags), IsTargetMethod);
|
||||||
|
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsTargetMethod(MethodInfo arg)
|
||||||
|
{
|
||||||
|
var parameters = arg.GetParameters();
|
||||||
|
return parameters.Length > 4
|
||||||
|
&& parameters[0]?.Name == "profileId"
|
||||||
|
&& parameters[1]?.Name == "savageProfile"
|
||||||
|
&& parameters[2]?.Name == "location"
|
||||||
|
&& arg.ReturnType == typeof(void);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix(string profileId, RaidSettings ____raidSettings, TarkovApplication __instance, Result<ExitStatus, TimeSpan, ClientMetrics> result)
|
||||||
|
{
|
||||||
|
// Get scav or pmc profile based on IsScav value
|
||||||
|
var profile = (____raidSettings.IsScav)
|
||||||
|
? PatchConstants.BackEndSession.ProfileOfPet
|
||||||
|
: PatchConstants.BackEndSession.Profile;
|
||||||
|
|
||||||
|
SaveProfileRequest request = new SaveProfileRequest
|
||||||
|
{
|
||||||
|
Exit = result.Value0.ToString().ToLowerInvariant(),
|
||||||
|
Profile = profile,
|
||||||
|
Health = Utils.Healing.HealthListener.Instance.CurrentHealth,
|
||||||
|
IsPlayerScav = ____raidSettings.IsScav
|
||||||
|
};
|
||||||
|
|
||||||
|
RequestHandler.PutJson("/raid/profile/save", request.ToJson(_defaultJsonConverters.AddItem(new NotesJsonConverter()).ToArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT;
|
||||||
|
using EFT.Game.Spawning;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Progression
|
||||||
|
{
|
||||||
|
public class OfflineSpawnPointPatch : ModulePatch
|
||||||
|
{
|
||||||
|
static OfflineSpawnPointPatch()
|
||||||
|
{
|
||||||
|
_ = nameof(ISpawnPoints.CreateSpawnPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var desiredType = PatchConstants.EftTypes.First(IsTargetType);
|
||||||
|
var desiredMethod = desiredType
|
||||||
|
.GetMethods(PatchConstants.PrivateFlags)
|
||||||
|
.First(m => m.Name.Contains("SelectSpawnPoint"));
|
||||||
|
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsTargetType(Type type)
|
||||||
|
{
|
||||||
|
// GClass1812 as of 17349
|
||||||
|
// GClass1886 as of 18876
|
||||||
|
return (type.GetMethods(PatchConstants.PrivateFlags).Any(x => x.Name.IndexOf("CheckFarthestFromOtherPlayers", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
&& type.IsClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(
|
||||||
|
ref ISpawnPoint __result,
|
||||||
|
object __instance,
|
||||||
|
ESpawnCategory category,
|
||||||
|
EPlayerSide side,
|
||||||
|
string groupId,
|
||||||
|
IAIDetails person,
|
||||||
|
string infiltration)
|
||||||
|
{
|
||||||
|
var spawnPointsField = (ISpawnPoints)__instance.GetType().GetFields(PatchConstants.PrivateFlags).SingleOrDefault(f => f.FieldType == typeof(ISpawnPoints))?.GetValue(__instance);
|
||||||
|
if (spawnPointsField == null)
|
||||||
|
{
|
||||||
|
throw new Exception($"OfflineSpawnPointPatch: Failed to locate field of {nameof(ISpawnPoints)} on class instance ({__instance.GetType().Name})");
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapSpawnPoints = spawnPointsField.ToList();
|
||||||
|
var unfilteredFilteredSpawnPoints = mapSpawnPoints.ToList();
|
||||||
|
|
||||||
|
// filter by e.g. 'Boiler Tanks' (always seems to be map name?)
|
||||||
|
if (!string.IsNullOrEmpty(infiltration))
|
||||||
|
{
|
||||||
|
mapSpawnPoints = mapSpawnPoints.Where(sp => sp?.Infiltration != null && (string.IsNullOrEmpty(infiltration) || sp.Infiltration.Equals(infiltration))).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
mapSpawnPoints = FilterByPlayerSide(mapSpawnPoints, category, side);
|
||||||
|
|
||||||
|
__result = mapSpawnPoints.Count == 0
|
||||||
|
? GetFallBackSpawnPoint(unfilteredFilteredSpawnPoints, category, side, infiltration)
|
||||||
|
: mapSpawnPoints.RandomElement();
|
||||||
|
|
||||||
|
Logger.LogInfo($"Desired spawnpoint: [{category}] [{side}] [{infiltration}]");
|
||||||
|
Logger.LogInfo($"PatchPrefix SelectSpawnPoint: [{__result.Id}] [{__result.Name}] [{__result.Categories}] [{__result.Sides}] [{__result.Infiltration}]");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ISpawnPoint> FilterByPlayerSide(List<ISpawnPoint> mapSpawnPoints, ESpawnCategory category, EPlayerSide side)
|
||||||
|
{
|
||||||
|
// Filter by category 'player' and by side ('usec', 'bear')
|
||||||
|
return mapSpawnPoints.Where(sp => sp.Categories.Contain(category) && sp.Sides.Contain(side)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ISpawnPoint GetFallBackSpawnPoint(List<ISpawnPoint> spawnPoints, ESpawnCategory category, EPlayerSide side, string infiltration)
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"PatchPrefix SelectSpawnPoint: Couldn't find any spawn points for: {category} | {side} | {infiltration} using random filtered spawn instead");
|
||||||
|
return spawnPoints.Where(sp => sp.Categories.Contain(ESpawnCategory.Player)).RandomElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
project/Aki.SinglePlayer/Patches/Quests/DogtagPatch.cs
Normal file
73
project/Aki.SinglePlayer/Patches/Quests/DogtagPatch.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT;
|
||||||
|
using EFT.InventoryLogic;
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Quests
|
||||||
|
{
|
||||||
|
public class DogtagPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static BindingFlags _flags;
|
||||||
|
private static PropertyInfo _getEquipmentProperty;
|
||||||
|
|
||||||
|
static DogtagPatch()
|
||||||
|
{
|
||||||
|
_ = nameof(EquipmentClass.GetSlot);
|
||||||
|
_ = nameof(DamageInfo.Weapon);
|
||||||
|
|
||||||
|
_flags = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||||
|
_getEquipmentProperty = typeof(Player).GetProperty("Equipment", _flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return typeof(Player).GetMethod("OnBeenKilledByAggressor", _flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(Player __instance, Player aggressor, DamageInfo damageInfo)
|
||||||
|
{
|
||||||
|
if (__instance.Profile.Info.Side == EPlayerSide.Savage)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var equipment = (EquipmentClass)_getEquipmentProperty.GetValue(__instance);
|
||||||
|
var dogtagSlot = equipment.GetSlot(EquipmentSlot.Dogtag);
|
||||||
|
var dogtagItem = dogtagSlot.ContainedItem;
|
||||||
|
|
||||||
|
if (dogtagItem == null)
|
||||||
|
{
|
||||||
|
Logger.LogError("DogtagPatch error > DogTag slot item is null somehow.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemComponent = dogtagItem.GetItemComponent<DogtagComponent>();
|
||||||
|
|
||||||
|
if (itemComponent == null)
|
||||||
|
{
|
||||||
|
Logger.LogError("DogtagPatch error > DogTagComponent on dog tag slot is null. Something went horrifically wrong!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var victimProfileInfo = __instance.Profile.Info;
|
||||||
|
|
||||||
|
itemComponent.AccountId = __instance.Profile.AccountId;
|
||||||
|
itemComponent.ProfileId = __instance.Profile.Id;
|
||||||
|
itemComponent.Nickname = victimProfileInfo.Nickname;
|
||||||
|
itemComponent.Side = victimProfileInfo.Side;
|
||||||
|
itemComponent.KillerName = aggressor.Profile.Info.Nickname;
|
||||||
|
itemComponent.Time = DateTime.Now;
|
||||||
|
itemComponent.Status = "Killed by ";
|
||||||
|
itemComponent.KillerAccountId = aggressor.Profile.AccountId;
|
||||||
|
itemComponent.KillerProfileId = aggressor.Profile.Id;
|
||||||
|
itemComponent.WeaponName = damageInfo.Weapon.Name;
|
||||||
|
|
||||||
|
if (__instance.Profile.Info.Experience > 0)
|
||||||
|
{
|
||||||
|
itemComponent.Level = victimProfileInfo.Level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
project/Aki.SinglePlayer/Patches/Quests/EndByTimerPatch.cs
Normal file
55
project/Aki.SinglePlayer/Patches/Quests/EndByTimerPatch.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Quests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Having the raid timer reach zero results in a successful extract,
|
||||||
|
/// this patch makes it so letting the time reach zero results in a MIA result
|
||||||
|
/// </summary>
|
||||||
|
public class EndByTimerPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var desiredType = PatchConstants.LocalGameType.BaseType;
|
||||||
|
var desiredMethod = desiredType.GetMethods(PatchConstants.PrivateFlags).SingleOrDefault(IsStopRaidMethod);
|
||||||
|
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsStopRaidMethod(MethodInfo mi)
|
||||||
|
{
|
||||||
|
var parameters = mi.GetParameters();
|
||||||
|
return (parameters.Length == 4
|
||||||
|
&& parameters[0].Name == "profileId"
|
||||||
|
&& parameters[1].Name == "exitStatus"
|
||||||
|
&& parameters[2].Name == "exitName"
|
||||||
|
&& parameters[3].Name == "delay"
|
||||||
|
&& parameters[0].ParameterType == typeof(string)
|
||||||
|
&& parameters[1].ParameterType == typeof(ExitStatus)
|
||||||
|
&& parameters[2].ParameterType == typeof(string)
|
||||||
|
&& parameters[3].ParameterType == typeof(float));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PrefixPatch(object __instance, ref ExitStatus exitStatus, ref string exitName)
|
||||||
|
{
|
||||||
|
// No extract name and successful, its a MIA
|
||||||
|
if (string.IsNullOrEmpty(exitName?.Trim()) && exitStatus == ExitStatus.Survived)
|
||||||
|
{
|
||||||
|
exitStatus = ExitStatus.MissingInAction;
|
||||||
|
exitName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // Do original
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
project/Aki.SinglePlayer/Patches/Quests/SpawnPmcPatch.cs
Normal file
49
project/Aki.SinglePlayer/Patches/Quests/SpawnPmcPatch.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.Quests
|
||||||
|
{
|
||||||
|
public class SpawnPmcPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var desiredType = PatchConstants.EftTypes.Single(IsTargetType);
|
||||||
|
var desiredMethod = desiredType.GetMethod("method_1", PatchConstants.PrivateFlags);
|
||||||
|
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||||
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsTargetType(Type type)
|
||||||
|
{
|
||||||
|
if (!typeof(IBotData).IsAssignableFrom(type) || type.GetMethod("method_1", PatchConstants.PrivateFlags) == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields = type.GetFields(PatchConstants.PrivateFlags);
|
||||||
|
return fields.Any(f => f.FieldType != typeof(WildSpawnType)) && fields.Any(f => f.FieldType == typeof(BotDifficulty));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref bool __result, object __instance, WildSpawnType ___wildSpawnType_0, BotDifficulty ___botDifficulty_0, Profile x)
|
||||||
|
{
|
||||||
|
if (x == null)
|
||||||
|
{
|
||||||
|
__result = false;
|
||||||
|
Logger.LogInfo($"profile x was null, ___wildSpawnType_0 = {___wildSpawnType_0}");
|
||||||
|
return false; // Skip original
|
||||||
|
}
|
||||||
|
|
||||||
|
__result = x.Info.Settings.Role == ___wildSpawnType_0 && x.Info.Settings.BotDifficulty == ___botDifficulty_0;
|
||||||
|
|
||||||
|
return false; // Skip original
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user