Initial commit

This commit is contained in:
Lennard Fonteijn 2023-04-25 01:16:58 +02:00
commit 673b59c087
144 changed files with 6132 additions and 0 deletions

107
.editorconfig Normal file
View File

@ -0,0 +1,107 @@
[*.*]
dotnet_analyzer_diagnostic.severity = default
[*.{cs,vb}]
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = error
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = error
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = error
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_allow_multiple_blank_lines_experimental = true:error
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
dotnet_code_quality_unused_parameters = all:suggestion
indent_style = space
[*.cs]
csharp_using_directive_placement = outside_namespace:error
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:error
csharp_style_namespace_declarations = block_scoped:error
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_throw_expression = true:suggestion
csharp_indent_labels = one_less_than_current
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
dotnet_diagnostic.IDE0240.severity = silent
dotnet_diagnostic.IDE0241.severity = silent
dotnet_diagnostic.IDE0005.severity = error

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
# Generic
bin
obj
.vs
*.user
# Server
/Src/HM5.Server/Logs
# Other
/Res

35
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,35 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/Src/HM5.Server/bin/Debug/net6.0/HM5.Server.dll",
"args": [],
"cwd": "${workspaceFolder}/Src/HM5.Server",
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

39
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,39 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "shell",
"args": [
"build",
"Src\\HM5.Server\\HM5.Server.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"group": "build",
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
},
{
"label": "test",
"command": "dotnet",
"type": "shell",
"args": [
"test",
"Src\\HM5.Test\\HM5.Test.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"group": "test",
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
}
]
}

70
HM5.sln Normal file
View File

@ -0,0 +1,70 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33122.133
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HM5.Server", "Src\HM5.Server\HM5.Server.csproj", "{6FA95780-4890-46C7-9342-C5CC7E465EEF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{43E1351B-28D0-45FE-A10A-545CFE650039}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MH5.Hook", "Src\HM5.Hook\MH5.Hook.vcxproj", "{57ABB826-E2F3-4908-BBBB-634C1A747CAA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HM5.Test", "Src\HM5.Test\HM5.Test.csproj", "{815C296D-7137-4DC6-96FF-4EC7432CEA1F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6FA95780-4890-46C7-9342-C5CC7E465EEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FA95780-4890-46C7-9342-C5CC7E465EEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FA95780-4890-46C7-9342-C5CC7E465EEF}.Debug|x64.ActiveCfg = Debug|Any CPU
{6FA95780-4890-46C7-9342-C5CC7E465EEF}.Debug|x64.Build.0 = Debug|Any CPU
{6FA95780-4890-46C7-9342-C5CC7E465EEF}.Debug|x86.ActiveCfg = Debug|Any CPU
{6FA95780-4890-46C7-9342-C5CC7E465EEF}.Debug|x86.Build.0 = Debug|Any CPU
{6FA95780-4890-46C7-9342-C5CC7E465EEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FA95780-4890-46C7-9342-C5CC7E465EEF}.Release|Any CPU.Build.0 = Release|Any CPU
{6FA95780-4890-46C7-9342-C5CC7E465EEF}.Release|x64.ActiveCfg = Release|Any CPU
{6FA95780-4890-46C7-9342-C5CC7E465EEF}.Release|x64.Build.0 = Release|Any CPU
{6FA95780-4890-46C7-9342-C5CC7E465EEF}.Release|x86.ActiveCfg = Release|Any CPU
{6FA95780-4890-46C7-9342-C5CC7E465EEF}.Release|x86.Build.0 = Release|Any CPU
{57ABB826-E2F3-4908-BBBB-634C1A747CAA}.Debug|Any CPU.ActiveCfg = Debug|Win32
{57ABB826-E2F3-4908-BBBB-634C1A747CAA}.Debug|Any CPU.Build.0 = Debug|Win32
{57ABB826-E2F3-4908-BBBB-634C1A747CAA}.Debug|x64.ActiveCfg = Debug|x64
{57ABB826-E2F3-4908-BBBB-634C1A747CAA}.Debug|x64.Build.0 = Debug|x64
{57ABB826-E2F3-4908-BBBB-634C1A747CAA}.Debug|x86.ActiveCfg = Debug|Win32
{57ABB826-E2F3-4908-BBBB-634C1A747CAA}.Debug|x86.Build.0 = Debug|Win32
{57ABB826-E2F3-4908-BBBB-634C1A747CAA}.Release|Any CPU.ActiveCfg = Release|Win32
{57ABB826-E2F3-4908-BBBB-634C1A747CAA}.Release|Any CPU.Build.0 = Release|Win32
{57ABB826-E2F3-4908-BBBB-634C1A747CAA}.Release|x64.ActiveCfg = Release|x64
{57ABB826-E2F3-4908-BBBB-634C1A747CAA}.Release|x64.Build.0 = Release|x64
{57ABB826-E2F3-4908-BBBB-634C1A747CAA}.Release|x86.ActiveCfg = Release|Win32
{57ABB826-E2F3-4908-BBBB-634C1A747CAA}.Release|x86.Build.0 = Release|Win32
{815C296D-7137-4DC6-96FF-4EC7432CEA1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{815C296D-7137-4DC6-96FF-4EC7432CEA1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{815C296D-7137-4DC6-96FF-4EC7432CEA1F}.Debug|x64.ActiveCfg = Debug|Any CPU
{815C296D-7137-4DC6-96FF-4EC7432CEA1F}.Debug|x64.Build.0 = Debug|Any CPU
{815C296D-7137-4DC6-96FF-4EC7432CEA1F}.Debug|x86.ActiveCfg = Debug|Any CPU
{815C296D-7137-4DC6-96FF-4EC7432CEA1F}.Debug|x86.Build.0 = Debug|Any CPU
{815C296D-7137-4DC6-96FF-4EC7432CEA1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{815C296D-7137-4DC6-96FF-4EC7432CEA1F}.Release|Any CPU.Build.0 = Release|Any CPU
{815C296D-7137-4DC6-96FF-4EC7432CEA1F}.Release|x64.ActiveCfg = Release|Any CPU
{815C296D-7137-4DC6-96FF-4EC7432CEA1F}.Release|x64.Build.0 = Release|Any CPU
{815C296D-7137-4DC6-96FF-4EC7432CEA1F}.Release|x86.ActiveCfg = Release|Any CPU
{815C296D-7137-4DC6-96FF-4EC7432CEA1F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {742A6A34-A300-4A5A-BD85-0CBCC7421E7A}
EndGlobalSection
EndGlobal

22
HM5.sln.DotSettings Normal file
View File

@ -0,0 +1,22 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DLC/@EntryIndexedValue">DLC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OS/@EntryIndexedValue">OS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UTC/@EntryIndexedValue">UTC</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=categoryid/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=checkpointid/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=contractid/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=contractname/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Deinitialize/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=dlctokens/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Hitman/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Leaderboard/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=leaderboardid/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=leaderboardtype/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=levelindex/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=metacategories/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=startindex/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=startingoutfittoken/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=startingweapontoken/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=tabgroup/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Templated/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

188
README.md Normal file
View File

@ -0,0 +1,188 @@
# Hitman Absolution & Sniper Challenge Server
This project emulates the server used by [Hitman: Absolution](https://store.steampowered.com/app/203140/Hitman_Absolution/) and the [Hitman: Sniper Challenge](https://steamcommunity.com/app/205930) and aims to restore all its features, including the Contracts feature.
# Getting started with the code
In order to get started with the code, you need to have one of the following IDEs/editors installed:
- Visual Studio 2022 (recommended)
- Jetbrains Rider
- Visual Studio Code with [vscode-solution-explorer](https://marketplace.visualstudio.com/items?itemName=fernandoescolar.vscode-solution-explorer) (recommended) and [C#](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp) (required) installed.
On top of that, you need to have the [.NET SDK](https://dotnet.microsoft.com/en-us/download) for your OS installed (**not** the Runtime). The current project is based on .NET 6 LTS. You can however install any newer version you like.
## Getting starting with the hook
The dinput8.dll hook is based on C++. It's beyond the scope of this document to explain how to compile that.
Once you have a x32 bit compiled version however, just drop it in the game folder along with the `hook.ini` file, make changes accordingly and start the game.
The hook supports both Hitman Absolution and Hitman Sniper Challenge depending on what you set in the `hook.ini`. Both allow to change the WebService URL. Hitman Absolution additionally allows you to skip the launcher.
The hook does not *crack* the games in any sort of way, you still need a legitimate copy in order to make use of this project.
# TODO
In its current state, almost everything is reverse engineered and working with mocked data. But there are still a few things to figure out to restore the full experience.
## Server
Phase 1:
- ~~Figure out if game can actually POST a body~~ Nope! It can't.
- ~~Figure out if GetFeaturedContract is actually used~~ Yes! After loading a level in Singleplayer. Will be shown on the in-game menu.
- ~~Figure out if GetScoreComparison is acctually used~~ Yes! After loading a level in Singleplayer. Will be shown on the in-game HUD at the start of a level.
- ~~Figure out if __metadata is even needed anywhere~~ Nope! Doesn't seem like it at least.
- Figure out the MessageTemplateIds
- Add Unit Tests based on real requests made by the game.
Phase 2:
- Add EntityFramework and define a database schema
- Replace all mocked with actual functionality
Phase 3:
- Expose leaderboards through Web UI
## Hook
- Figure out how to patch Hitman Sniper Challenge without triggering anti-debugging measures to skip launcher.
- IDEA: Hook Steam Friends API to hook up own account system with friends.
# Notes about Ids in-game
- UserId is almost always a 64-bit SteamId. However, to stay on the safe side all UserId-properties are treated as a String.
- ContractId is considered a String in-game, since `Play_01` is used by the Contracts tutorial. This also has a effect on the LeaderboardId-properties, since that property can be a ContractId if the game wants to receive Contract-specific leaderboards.
- IDEA: Use the hook to change these ID's to a negative number so the data model can keep using Integers.
# Documentation
This section aims to explain the inner workings of the game in relation to the API endpoints that need to be reverse engineered.
## Startup Phase
The first interesting calls happen here:
- `ZOnlineManagerWindows::Update`
- `ZOnlineManager::Update`
- Called near a `_SteamAPI_RunCallbacks`-call
- Contains a lot of timed events that call API endpoints regularly
If the webservice is not connected yet, it will call `ZOSWebService::Connect` followed by ` OSuite::ZOnlineSuite::CreateWebServiceClient`. In this function the configured service-url is passed to `OSuite::ZWebServiceClientManager::Create`.
Since there is no instance for this webservice yet, it will go into `OSuite::ZWebServiceClient::Initialize`. This function registers the `os_getStatus` and `os_$metadata` endpoints with a callback to `OSuite::ZWebServiceClient::InternalProbeResultCallback`. Eventually, `OSuite::ZWebServiceClient::ProbeAvailability` is called.
`OSuite::ZWebServiceClient::ProbeAvailability` will prepare the request for the `os_getStatus` endpoint and use `OSuite::ZWebServiceClient::InternalProbeCallback` as a callback. This callback is responsible for parsing the response.
If succesfully parsed, `OSuite::ZWebServiceClient::InternalProbeResultCallback` is called which will prepare the request to the `os_$metadata` endpoint with `OSuite::ZWebServiceClient::InternalMetadataCallback` as a callback.
Unlike the `os_getStatus` request, a cache is used for the metadata and `OSuite::ZAtomCache::Open<OSuite::ZOMetadata>` will be called to fetch the result from the endpoint. The response is parsed (see the notes about `OSuite::ZAtomCache::Open` below) and the `OSuite::ZWebServiceClient::InternalMetadataCallback` callback is called.
This function will check if a valid Metadata-response was given through `OSuite::ZWebServiceClient::RetrieveRequest<OSuite::ZOMetadata>` and if there was, flag the ZWebServiceClient as connected (`m_eStatus = 2 //READY_STATE`)
## OSuite::ZAtomCache::Open
Something very important happens in any `OSuite::ZAtomCache::Open<T>`-function, as it will make an instance of a `TAtomObject<T>`. When the response comes back from the endpoint, this instance will get its `Read`-function called, which will then make an instance of the generic type.
There are a few of these `Read`-functions responsible for creating instances for:
- `OSuite::ZOEntry`
- `OSuite::ZOFeed`
- `OSuite::ZOMetadata`
- `OSuite::ZOServiceOperationResult`
Any of these constructors will call their respective `ParseJsonValue`-function.
## Invoking requests
- `ZOSServiceOperation::Invoke` with callback
- Callback can be used to find the expected response-type based on usage of the following functions:
- `OSuite::ZWebServiceClient::RetrieveRequest<OSuite::ZOFeed>`
- `OSuite::ZWebServiceClient::RetrieveRequest<OSuite::ZOEntry>`
- `OSuite::ZWebServiceClient::RetrieveRequest<OSuite::ZServiceOperationResult>`
- See ReturnType below to find out what to set ReturnType to.
- `ZOSQueryManager::Push` => `ZOSServiceOperation::Execute` => `OSuite::ZWebServiceClient::ExecuteQuery`
`OSuite::ZWebServiceClient::ExecuteQuery` will the try to get API endpoint. It bases this on the QueryMode, which can either be:
- QM_NONE //0
- QM_ENTITYSET //1
- QM_FUNCTIONIMPORT //2
## ExecuteQuery with QM_NONE
This will always generate an internal 404, which causes the webservice to disconnect and show the Disconnected-dialog in-game (this happens with the `ShowDialog` in `ZOnlineManager::Update`).
## ExecuteQuery with QM_FUNCTIONIMPORT
`OSuite::ZOMetadata::FunctionImport` will be called and if this fails an internal 404 is generated.
It will then check if the FunctionImport has all the querystring-parameters specified. If something is missing, again an internal 404 is generated.
Otherwise, it will continue and call the endpoint based on the data from the FunctionImport. It can make either a GET or a POST request (`HttpMethod`). Based on the `ReturnType` of the FunctionImport it will decide to either expect a `ZOEntry`, `ZOServiceOperationResult` or a `ZOFeed`.
## ExecuteQuery with QM_ENTITYSET
`OSuite::ZOMetadata::EntitySet` will be called and always result in a GET-request for a `ZOFeed`.
# ReturnType
These are the different ReturnType:
- SVOP_FEED = 0x0 => `OSuite::ZOFeed`
- SVOP_ENTRY = 0x1 => `OSuite::ZOEntry`
- SVOP_VALUE = 0x2 => `OSuite::ZOServiceOperationResult`
- SVOP_VALUECOLLECTION = 0x3
- SVOP_VOID = 0x4
The following pseudocode shows how the game will convert the value of the `ReturnType` on a `EdmFunctionImport` to a ReturnType enumeration value:
```
this->entityName = ReturnType;
this->returnType = SVOP_VOID;
var isEntityType = !this->entityName.Contains("Edm.")
if(this->entityName->StartsWith("Collection"))
{
if(isEntityType) {
this->returnType = SVOP_FEED
}
else {
this->returnType = SVOP_VALUECOLLECTION
}
this->entityName = "Collection(this->entityName)"
}
else if(isEntityType) {
this->returnType = SVOP_ENTRY
}
else {
this->returnType = SVOP_VALUE
}
```
# Json parsing
JSONTYPE_STRING = 0x0,
JSONTYPE_OBJECT = 0x1,
JSONTYPE_ARRAY = 0x2,
When `OSuite::ZAtomBase::ParseJson` is called:
- It will loop over all key-value pairs of the JSON-object that is passed in as the second argument.
- Each key-value pair will be passed to a `ParseJsonValue`-function, which is determined by the type of `ZAtomBase`-object passed in as the first argument.
There are a few of the `ZAtomBase`-objects in the game:
- `ZServiceOperationValue`
- `ZServiceOperationResult`
- `ZAtomFeed`
- `ZAtomEntry`
- `ZOMetadata`
The `ZOMetadata`-object is responsible for parsing the metadata-response from the API. It will call the `OSuite::ZOMetadata::ParseSchema`, which will call the `ParseJson`-function, which will call the `ParseJsonValue`-function of passed-in `ZEdmBase`-object.
There are a few of the `ZEdmBase`-objects in the game:
- `ZOEdmEntityType`
- `ZOEdmComplexType`
- `ZOEdmAssociation`
- `ZOEdmFunctionImport`
- `ZOEdmClientConfiguration`
- NOTE: Instanced from `ZOMetadata->ParseJsonValue`
## ZOEntry
- ZOEntry is always an EdmEntityType
- Can contain a "results" which then contains the ZOEntry, but this is not required.
## ZOFeed
- ZOFeed appears to support object and literal value. Not sure about array.
- object should contain a "results" and "__count", results is then treated as an array of ZOEntry.
## ZOServiceOperationResult
- ZOServiceOperationResult can be a single value or an array of values
- All values will be converted to a ZServiceOperationValue
- In-memory a ZOServiceOperationResult is always a list of 1 or more ZServiceOperationValue
- ZServiceOperationValue can be a single value or an object
# Scratchpad
These are just some random notes.
- `OSuite::ZOEntry::ParseJsonValue`, `OSuite::ZOFeed::ParseJsonValue` and `OSuite::ZOServiceOperationResult::ParseJsonValue` describe how to parse their respective type.
- The use of `OSuite::ZOEntry::Property` describes an expected property on a `ZOEntry`.
- The second argument of `OSuite::ZOQuery::EntitySet` is the name of the expected EntitySet,
- The first argument of `ZOSServiceOperation::Invoke` is the name of the expected `EdmImportFunction`

441
Src/HM5.Hook/INIReader.h Normal file
View File

@ -0,0 +1,441 @@
// Read an INI file into easy-to-access name/value pairs.
// inih and INIReader are released under the New BSD license (see LICENSE.txt).
// Go to the project home page for more info:
//
// https://github.com/benhoyt/inih
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef __INI_H__
#define __INI_H__
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
/* Typedef for prototype of handler function. */
typedef int(*ini_handler)(void* user, const char* section,
const char* name, const char* value);
/* Typedef for prototype of fgets-style reader function. */
typedef char* (*ini_reader)(char* str, int num, void* stream);
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's configparser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
int ini_parse(const char* filename, ini_handler handler, void* user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
int ini_parse_file(FILE* file, ini_handler handler, void* user);
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
filename. Used for implementing custom or string-based I/O. */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
configparser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif
/* Nonzero to allow inline comments (with valid inline comment characters
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif
/* Nonzero to use stack, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif
/* Maximum line length for any line in INI file. */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif
#ifdef __cplusplus
}
#endif
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#if !INI_USE_STACK
#include <stdlib.h>
#endif
#define MAX_SECTION 100
#define MAX_NAME 100
/* Strip whitespace chars off end of given string, in place. Return s. */
inline static char* rstrip(char* s)
{
char* p = s + strlen(s);
while (p > s && isspace((unsigned char)(*--p)))
*p = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
inline static char* lskip(const char* s)
{
while (*s && isspace((unsigned char)(*s)))
s++;
return (char*)s;
}
/* Return pointer to first char (of chars) or inline comment in given string,
or pointer to null at end of string if neither found. Inline comment must
be prefixed by a whitespace character to register as a comment. */
inline static char* find_chars_or_comment(const char* s, const char* chars)
{
#if INI_ALLOW_INLINE_COMMENTS
int was_space = 0;
while (*s && (!chars || !strchr(chars, *s)) &&
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
was_space = isspace((unsigned char)(*s));
s++;
}
#else
while (*s && (!chars || !strchr(chars, *s))) {
s++;
}
#endif
return (char*)s;
}
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
inline static char* strncpy0(char* dest, const char* src, size_t size)
{
strncpy(dest, src, size);
dest[size - 1] = '\0';
return dest;
}
/* See documentation in header file. */
inline int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user)
{
/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
char line[INI_MAX_LINE];
#else
char* line;
#endif
char section[MAX_SECTION] = "";
char prev_name[MAX_NAME] = "";
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
#if !INI_USE_STACK
line = (char*)malloc(INI_MAX_LINE);
if (!line) {
return -2;
}
#endif
/* Scan through stream line by line */
while (reader(line, INI_MAX_LINE, stream) != NULL) {
lineno++;
start = line;
#if INI_ALLOW_BOM
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
#endif
start = lskip(rstrip(start));
if (*start == ';' || *start == '#') {
/* Per Python configparser, allow both ; and # comments at the
start of a line */
}
#if INI_ALLOW_MULTILINE
else if (*prev_name && *start && start > line) {
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(start, NULL);
if (*end)
*end = '\0';
rstrip(start);
#endif
/* Non-blank line with leading whitespace, treat as continuation
of previous name's value (as per Python configparser). */
if (!handler(user, section, prev_name, start) && !error)
error = lineno;
}
#endif
else if (*start == '[') {
/* A "[section]" line */
end = find_chars_or_comment(start + 1, "]");
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
*prev_name = '\0';
}
else if (!error) {
/* No ']' found on section line */
error = lineno;
}
}
else if (*start) {
/* Not a comment, must be a name[=:]value pair */
end = find_chars_or_comment(start, "=:");
if (*end == '=' || *end == ':') {
*end = '\0';
name = rstrip(start);
value = lskip(end + 1);
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(value, NULL);
if (*end)
*end = '\0';
#endif
rstrip(value);
/* Valid name[=:]value pair found, call handler */
strncpy0(prev_name, name, sizeof(prev_name));
if (!handler(user, section, name, value) && !error)
error = lineno;
}
else if (!error) {
/* No '=' or ':' found on name[=:]value line */
error = lineno;
}
}
#if INI_STOP_ON_FIRST_ERROR
if (error)
break;
#endif
}
#if !INI_USE_STACK
free(line);
#endif
return error;
}
/* See documentation in header file. */
inline int ini_parse_file(FILE* file, ini_handler handler, void* user)
{
return ini_parse_stream((ini_reader)fgets, file, handler, user);
}
/* See documentation in header file. */
inline int ini_parse(const char* filename, ini_handler handler, void* user)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}
#endif /* __INI_H__ */
#ifndef __INIREADER_H__
#define __INIREADER_H__
#include <map>
#include <set>
#include <string>
// Read an INI file into easy-to-access name/value pairs. (Note that I've gone
// for simplicity here rather than speed, but it should be pretty decent.)
class INIReader
{
public:
// Empty Constructor
INIReader() {};
// Construct INIReader and parse given filename. See ini.h for more info
// about the parsing.
INIReader(std::string filename);
// Return the result of ini_parse(), i.e., 0 on success, line number of
// first error on parse error, or -1 on file open error.
int ParseError() const;
// Return the list of sections found in ini file
const std::set<std::string>& Sections() const;
// Get a string value from INI file, returning default_value if not found.
std::string Get(std::string section, std::string name,
std::string default_value) const;
// Get an integer (long) value from INI file, returning default_value if
// not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2").
long GetInteger(std::string section, std::string name, long default_value) const;
// Get a real (floating point double) value from INI file, returning
// default_value if not found or not a valid floating point value
// according to strtod().
double GetReal(std::string section, std::string name, double default_value) const;
// Get a boolean value from INI file, returning default_value if not found or if
// not a valid true/false value. Valid true values are "true", "yes", "on", "1",
// and valid false values are "false", "no", "off", "0" (not case sensitive).
bool GetBoolean(std::string section, std::string name, bool default_value) const;
protected:
int _error;
std::map<std::string, std::string> _values;
std::set<std::string> _sections;
static std::string MakeKey(std::string section, std::string name);
static int ValueHandler(void* user, const char* section, const char* name,
const char* value);
};
#endif // __INIREADER_H__
#ifndef __INIREADER__
#define __INIREADER__
#include <algorithm>
#include <cctype>
#include <cstdlib>
using std::string;
inline INIReader::INIReader(string filename)
{
_error = ini_parse(filename.c_str(), ValueHandler, this);
}
inline int INIReader::ParseError() const
{
return _error;
}
inline const std::set<string>& INIReader::Sections() const
{
return _sections;
}
inline string INIReader::Get(string section, string name, string default_value) const
{
string key = MakeKey(section, name);
return _values.count(key) ? _values.at(key) : default_value;
}
inline long INIReader::GetInteger(string section, string name, long default_value) const
{
string valstr = Get(section, name, "");
const char* value = valstr.c_str();
char* end;
// This parses "1234" (decimal) and also "0x4D2" (hex)
long n = strtol(value, &end, 0);
return end > value ? n : default_value;
}
inline double INIReader::GetReal(string section, string name, double default_value) const
{
string valstr = Get(section, name, "");
const char* value = valstr.c_str();
char* end;
double n = strtod(value, &end);
return end > value ? n : default_value;
}
inline bool INIReader::GetBoolean(string section, string name, bool default_value) const
{
string valstr = Get(section, name, "");
// Convert to lower case to make string comparisons case-insensitive
std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower);
if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
return true;
else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0")
return false;
else
return default_value;
}
inline string INIReader::MakeKey(string section, string name)
{
string key = section + "=" + name;
// Convert to lower case to make section/name lookups case-insensitive
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
return key;
}
inline int INIReader::ValueHandler(void* user, const char* section, const char* name,
const char* value)
{
INIReader* reader = (INIReader*)user;
string key = MakeKey(section, name);
if (reader->_values[key].size() > 0)
reader->_values[key] += "\n";
reader->_values[key] += value;
reader->_sections.insert(section);
return 1;
}
#endif // __INIREADER__

View File

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="hook_absolution.cpp" />
<ClCompile Include="hook_sniper.cpp" />
<ClCompile Include="proxy.cpp" />
<ClCompile Include="proxy_dinput8.cpp" />
<ClCompile Include="hook.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="hook_absolution.h" />
<ClInclude Include="hook_sniper.h" />
<ClInclude Include="proxy.h" />
<ClInclude Include="hook.h" />
<ClInclude Include="INIReader.h" />
<ClInclude Include="MinHook.h" />
</ItemGroup>
<ItemGroup>
<Library Include="libMinHook-x64-v140-mt.lib" />
<Library Include="libMinHook-x86-v140-mt.lib" />
</ItemGroup>
<ItemGroup>
<None Include="hook.ini" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{57ABB826-E2F3-4908-BBBB-634C1A747CAA}</ProjectGuid>
<RootNamespace>MyDLL</RootNamespace>
<WindowsTargetPlatformVersion>10.0.22000.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<TargetName>dinput8</TargetName>
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<TargetName>dinput8</TargetName>
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<TargetName>dinput8</TargetName>
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetName>dinput8</TargetName>
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalOptions>/NODEFAULTLIB:LIBCMT.lib %(AdditionalOptions)</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="hook.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="proxy_dinput8.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="hook_absolution.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="proxy.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="hook_sniper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="hook.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="MinHook.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="INIReader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="proxy.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="hook_sniper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="hook_absolution.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Library Include="libMinHook-x64-v140-mt.lib" />
<Library Include="libMinHook-x86-v140-mt.lib" />
</ItemGroup>
<ItemGroup>
<None Include="hook.ini">
<Filter>Resource Files</Filter>
</None>
</ItemGroup>
</Project>

182
Src/HM5.Hook/MinHook.h Normal file
View File

@ -0,0 +1,182 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2015 Tsuda Kageyu.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#if !(defined _M_IX86) && !(defined _M_X64)
#error MinHook supports only x86 and x64 systems.
#endif
#include <windows.h>
// MinHook Error Codes.
typedef enum MH_STATUS
{
// Unknown error. Should not be returned.
MH_UNKNOWN = -1,
// Successful.
MH_OK = 0,
// MinHook is already initialized.
MH_ERROR_ALREADY_INITIALIZED,
// MinHook is not initialized yet, or already uninitialized.
MH_ERROR_NOT_INITIALIZED,
// The hook for the specified target function is already created.
MH_ERROR_ALREADY_CREATED,
// The hook for the specified target function is not created yet.
MH_ERROR_NOT_CREATED,
// The hook for the specified target function is already enabled.
MH_ERROR_ENABLED,
// The hook for the specified target function is not enabled yet, or already
// disabled.
MH_ERROR_DISABLED,
// The specified pointer is invalid. It points the address of non-allocated
// and/or non-executable region.
MH_ERROR_NOT_EXECUTABLE,
// The specified target function cannot be hooked.
MH_ERROR_UNSUPPORTED_FUNCTION,
// Failed to allocate memory.
MH_ERROR_MEMORY_ALLOC,
// Failed to change the memory protection.
MH_ERROR_MEMORY_PROTECT,
// The specified module is not loaded.
MH_ERROR_MODULE_NOT_FOUND,
// The specified function is not found.
MH_ERROR_FUNCTION_NOT_FOUND
}
MH_STATUS;
// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
// MH_QueueEnableHook or MH_QueueDisableHook.
#define MH_ALL_HOOKS NULL
#ifdef __cplusplus
extern "C" {
#endif
// Initialize the MinHook library. You must call this function EXACTLY ONCE
// at the beginning of your program.
MH_STATUS WINAPI MH_Initialize(VOID);
// Uninitialize the MinHook library. You must call this function EXACTLY
// ONCE at the end of your program.
MH_STATUS WINAPI MH_Uninitialize(VOID);
// Creates a Hook for the specified target function, in disabled state.
// Parameters:
// pTarget [in] A pointer to the target function, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal);
// Creates a Hook for the specified API function, in disabled state.
// Parameters:
// pszModule [in] A pointer to the loaded module name which contains the
// target function.
// pszTarget [in] A pointer to the target function name, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHookApi(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal);
// Removes an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
// Enables an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// enabled in one go.
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
// Disables an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// disabled in one go.
MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
// Queues to enable an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// queued to be enabled.
MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
// Queues to disable an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// queued to be disabled.
MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
// Applies all queued changes in one go.
MH_STATUS WINAPI MH_ApplyQueued(VOID);
// Translates the MH_STATUS to its name as a string.
const char * WINAPI MH_StatusToString(MH_STATUS status);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
template <typename T>
inline MH_STATUS MH_CreateHookEx(LPVOID pTarget, LPVOID pDetour, T** ppOriginal)
{
return MH_CreateHook(pTarget, pDetour, reinterpret_cast<LPVOID*>(ppOriginal));
}
template <typename T>
inline MH_STATUS MH_CreateHookApiEx(LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, T** ppOriginal)
{
return MH_CreateHookApi(pszModule, pszProcName, pDetour, reinterpret_cast<LPVOID*>(ppOriginal));
}
#endif

104
Src/HM5.Hook/hook.cpp Normal file
View File

@ -0,0 +1,104 @@
#include "hook.h"
#include "hook_absolution.h"
#include "hook_sniper.h"
#include "proxy.h"
bool OptionsEnabled = false;
bool OptionsLog = false;
IHook* Hook;
std::ofstream LogFile;
INIReader IniFile;
void InitializeOptions()
{
LogFile.open("hook.log", std::ios_base::out);
LogTime();
LogFile << "InitializeOptions" << std::endl;
IniFile = INIReader("hook.ini");
if (IniFile.ParseError() != 0) {
LogFile << "Can't load hook.ini!" << std::endl;
return;
}
OptionsEnabled = IniFile.GetBoolean("options", "enabled", false);
if (!OptionsEnabled)
{
return;
}
OptionsLog = IniFile.GetBoolean("options", "log", false);
auto IsHitmanAbsolution = strcmp(
IniFile.Get("options", "game", "hm5").c_str(),
"hm5"
) == 0;
if (IsHitmanAbsolution)
{
LogFile << "Hitman Absolution" << std::endl;
Hook = (IHook*)new AbsolutionHook();
}
else
{
LogFile << "Hitman Sniper Challenge" << std::endl;
Hook = (IHook*)new SniperHook();
}
Hook->InitializeOptions();
}
void InitializeHook()
{
LogFile << "InitializeHook" << std::endl;
LogStatus("Initialize", MH_Initialize());
Hook->PreInitializeHook();
}
void DeinitializeHook()
{
LogTime();
LogFile << "DeinitializeHook" << std::endl;
LogStatus("Deinitialize", MH_Uninitialize());
if (LogFile)
{
LogFile.close();
}
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD fdwReason, LPVOID /*lpReserved*/)
{
if (fdwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hModule);
InitializeOptions();
LoadProxyDll();
if (OptionsEnabled)
{
InitializeHook();
}
}
else if (fdwReason == DLL_PROCESS_DETACH)
{
if (OptionsEnabled)
{
DeinitializeHook();
}
}
return TRUE;
}

42
Src/HM5.Hook/hook.h Normal file
View File

@ -0,0 +1,42 @@
#pragma once
// ReSharper disable once CppInconsistentNaming
#define _CRT_SECURE_NO_WARNINGS // NOLINT(bugprone-reserved-identifier)
#include <Windows.h>
#include <fstream>
#include <ctime>
#include <map>
#include "MinHook.h"
#include "INIReader.h"
interface IHook {
virtual void InitializeOptions() = 0;
virtual void PreInitializeHook() = 0;
virtual void PostInitializeHook() = 0;
};
extern IHook* Hook;
extern bool OptionsEnabled;
extern bool OptionsLog;
extern std::ofstream LogFile;
extern INIReader IniFile;
inline void LogTime() {
std::time_t result = std::time(nullptr);
char* time = std::asctime(std::localtime(&result));
time[strlen(time) - 1] = 0;
LogFile << "[" << time << "]" << std::endl;
}
#define LOG_MESSAGE(...) if(OptionsLog) { LogTime();LogFile << __VA_ARGS__ << std::endl;}
inline void LogStatus(LPCSTR name, MH_STATUS status)
{
LogFile << name << ": " << MH_StatusToString(status) << std::endl;
}

13
Src/HM5.Hook/hook.ini Normal file
View File

@ -0,0 +1,13 @@
[options]
enabled=true
log=true
;Can be set to either hm5 or sniper
game=hm5
[hm5]
webserviceurl=http://localhost/hm5
skiplauncher=true
[sniper]
webserviceurl=http://localhost/sniper

View File

@ -0,0 +1,61 @@
#include "hook_absolution.h"
bool AbsolutionHook::SkipLauncher = false;
std::string AbsolutionHook::WebServiceUrl;
AbsolutionHook::tGetApplicationOptionBool AbsolutionHook::oGetApplicationOptionBool = nullptr;
AbsolutionHook::tZWebServiceClientManagerCreate AbsolutionHook::oZWebServiceClientManagerCreate = nullptr;
bool __cdecl AbsolutionHook::GetApplicationOptionBool(ZString* sName, bool bDefault)
{
if (strcmp(sName->m_chars, "EnableSplashScreen") == 0 && SkipLauncher)
{
LogFile << "SkipLauncher" << std::endl;
return false;
}
return oGetApplicationOptionBool(sName, bDefault);
}
//Source: https://tresp4sser.wordpress.com/2012/10/06/how-to-hook-thiscall-functions/
void* __fastcall AbsolutionHook::ZWebServiceClientManagerCreate(void* instance, void* _, const char* baseUrl, void* callback)
{
LogFile << "ZWebServiceClientManagerCreate" << baseUrl << std::endl;
return oZWebServiceClientManagerCreate(instance, WebServiceUrl.c_str(), callback);
}
void AbsolutionHook::InitializeOptions()
{
SkipLauncher = IniFile.GetBoolean("hm5", "skiplauncher", false);
WebServiceUrl = IniFile.Get("hm5", "webserviceurl", "http://localhost/hm5");
}
void AbsolutionHook::PreInitializeHook()
{
void* ptrGetApplicationOptionBool = (void*)0x00612930;
void* ptrZWebServiceClientManagerCreate = (void*)0x00BE22CF;
MH_STATUS skipLauncherHook = MH_CreateHook(
ptrGetApplicationOptionBool,
GetApplicationOptionBool,
reinterpret_cast<void**>(&oGetApplicationOptionBool)
);
MH_STATUS webServiceurlHook = MH_CreateHook(
ptrZWebServiceClientManagerCreate,
ZWebServiceClientManagerCreate,
reinterpret_cast<void**>(&oZWebServiceClientManagerCreate)
);
LogStatus("SkipLauncher hook", skipLauncherHook);
LogStatus("SkipLauncher enable", MH_EnableHook(ptrGetApplicationOptionBool));
LogStatus("WebServiceUrl hook", webServiceurlHook);
LogStatus("WebServiceUrl enable", MH_EnableHook(ptrZWebServiceClientManagerCreate));
}
void AbsolutionHook::PostInitializeHook()
{
//Do nothing
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "hook.h"
#include <string>
class AbsolutionHook : IHook
{
public:
void InitializeOptions() override;
void PreInitializeHook() override;
void PostInitializeHook() override;
private:
static bool SkipLauncher;
static std::string WebServiceUrl;
struct ZString
{
unsigned int m_length;
const char* m_chars;
};
typedef bool(__cdecl* tGetApplicationOptionBool)(ZString* sName, bool bDefault);
typedef void* (__thiscall* tZWebServiceClientManagerCreate)(void* instance, const char* baseUrl, void* callback);
static bool __cdecl GetApplicationOptionBool(ZString* sName, bool bDefault);
static void* __fastcall ZWebServiceClientManagerCreate(void* instance, void* _, const char* baseUrl, void* callback);
static tGetApplicationOptionBool oGetApplicationOptionBool;
static tZWebServiceClientManagerCreate oZWebServiceClientManagerCreate;
};

View File

@ -0,0 +1,17 @@
#include "hook_sniper.h"
void SniperHook::InitializeOptions()
{
WebServiceUrl = IniFile.Get("sniper", "webserviceurl", "http://localhost/sniper");
}
void SniperHook::PreInitializeHook()
{
//Do nothing
}
void SniperHook::PostInitializeHook()
{
auto ptrWebServiceUrl = (void*)0x113D5E8;
strcpy((char*)ptrWebServiceUrl, WebServiceUrl.substr(0, 256).c_str());
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "hook.h"
#include <string>
class SniperHook : IHook
{
public:
virtual void InitializeOptions();
virtual void PreInitializeHook();
virtual void PostInitializeHook();
private:
std::string WebServiceUrl;
};

Binary file not shown.

Binary file not shown.

10
Src/HM5.Hook/proxy.cpp Normal file
View File

@ -0,0 +1,10 @@
#include "proxy.h"
#if HOOK_TYPE == HOOK_TYPE_DISABLED
void LoadProxyDll()
{
//Do nothing
}
#endif

8
Src/HM5.Hook/proxy.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#define HOOK_TYPE_DISABLED 0
#define HOOK_TYPE_DINPUT8 1
#define HOOK_TYPE HOOK_TYPE_DINPUT8
void LoadProxyDll();

View File

@ -0,0 +1,43 @@
#include "proxy.h"
#if HOOK_TYPE == HOOK_TYPE_DINPUT8
#include "hook.h"
#include <iostream>
#include "MinHook.h"
typedef HRESULT(WINAPI* tDirectInput8Create)(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID* ppvOut, LPUNKNOWN punkOuter);
tDirectInput8Create oDirectInput8Create;
void LoadProxyDll()
{
LogFile << "LoadProxyDLL: ";
wchar_t syspath[512];
GetSystemDirectory(syspath, 512);
wcscat_s(syspath, L"\\dinput8.dll");
const HMODULE hLibrary = LoadLibrary(syspath);
if (!hLibrary) {
LogFile << "Fail!" << std::endl;
return;
}
oDirectInput8Create = (tDirectInput8Create)GetProcAddress(hLibrary, "DirectInput8Create");
LogFile << "Done!" << std::endl;
}
#pragma comment(linker, "/export:DirectInput8Create=_DirectInput8Create@20")
extern "C" __declspec(dllexport) HRESULT WINAPI DirectInput8Create(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID * ppvOut, LPUNKNOWN punkOuter)
{
LogFile << "DirectInput8Create" << std::endl;
Hook->PostInitializeHook();
return oDirectInput8Create(hinst, dwVersion, riidltf, ppvOut, punkOuter);
}
#endif

View File

@ -0,0 +1,13 @@
namespace HM5.Server.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class EdmEntityAttribute : Attribute
{
public string Name { get; set; }
public EdmEntityAttribute(string name)
{
Name = name;
}
}
}

View File

@ -0,0 +1,17 @@
namespace HM5.Server.Attributes
{
[AttributeUsage(AttributeTargets.Property)]
public class EdmPropertyAttribute : Attribute
{
public string Name { get; set; }
public string Type { get; set; }
public bool Nullable { get; set; }
public EdmPropertyAttribute(string name, string type, bool nullable)
{
Name = name;
Type = type;
Nullable = nullable;
}
}
}

View File

@ -0,0 +1,14 @@
using HM5.Server.Mvc;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Attributes
{
public class NormalizedStringAttribute : ModelBinderAttribute
{
public NormalizedStringAttribute(string propertyName = null)
: base(typeof(NormalizedStringModelBinder))
{
Name = propertyName;
}
}
}

View File

@ -0,0 +1,74 @@
using HM5.Server.Interfaces;
using HM5.Server.Models.Response;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers
{
public abstract class BaseController : Controller
{
protected readonly ISimpleLogger Logger;
protected BaseController(ISimpleLogger simpleLogger)
{
Logger = simpleLogger;
}
protected JsonResult JsonGenericResponse<T>(T data)
{
return Json(new BaseResponse<T>
{
Data = data
});
}
/**
* NOTE: Even though it is not constrained, only use primitive values for the generic!
*/
protected JsonResult JsonOperationValueResponse<T>(T data)
{
return Json(new BaseOperationValueResponse<T>
{
Data = new SingleOperationValue<T>
{
Key = data
}
});
}
/**
* NOTE: Even though it is not constrained, only use primitive values for the generic!
*/
protected JsonResult JsonOperationListResponse<T>(List<T> data)
{
return Json(new BaseOperationListResponse<T>
{
Data = data
});
}
protected JsonResult JsonEntryResponse<T>(T data)
where T : IEdmEntity
{
return Json(new BaseEntryResponse<T>
{
Data = new EntryObject<T>
{
Results = data
}
});
}
protected JsonResult JsonFeedResponse<T>(List<T> data)
where T : IEdmEntity
{
return Json(new BaseFeedResponse<T>
{
Data = new FeedObject<T>
{
Results = data,
Count = data.Count
}
});
}
}
}

View File

@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers
{
public class ErrorController : Controller
{
[Route("{*url}")]
public IActionResult CatchAll()
{
return NotFound();
}
}
}

View File

@ -0,0 +1,249 @@
using HM5.Server.Enums;
using HM5.Server.Interfaces;
using HM5.Server.Models;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
[Route("hm5")]
public partial class HitmanController : BaseController
{
public const string SchemaNamespace = "HM5";
//NOTE: Rating seems unused in UI?
private readonly List<ScoreEntry> _mockedGetScoresResponse = new()
{
new ScoreEntry
{
UserId = "LennardF1989",
Rank = 1,
Rating = 5,
Score = 1989
},
new ScoreEntry
{
UserId = "RDIL",
Rank = 2,
Rating = 5,
Score = 1989
},
new ScoreEntry
{
UserId = "AnthonyFuller",
Rank = 3,
Rating = 5,
Score = 1989
},
new ScoreEntry
{
//NOTE: SteamID
UserId = "76561198161220058",
Rank = 4,
Rating = 5,
Score = 1989
},
new ScoreEntry
{
//NOTE: CountryID
UserId = "73",
Rank = 5,
Rating = 5,
Score = 1989
}
};
private readonly List<int> _mockedGetAverageScoresResponse = new()
{
1, //World Average
2, //Country Average
3, //Friends Average
4 //Score: Deadliest / Richest / Most Popular Assassin
};
private readonly Contract _mockedContractWithoutCompetition = new()
{
Id = 1,
DisplayId = "FakeContract47",
UserId = "76561198161220058",
UserName = "Wingz of Death",
Title = "Kill Diana",
Description = "Lol, joke.",
HighestScoringFriendName = "76561198161220058",
HighestScoringFriendScore = 101,
Targets = new TargetsWrapper
{
Targets = new List<Target>
{
new()
{
Name = "David Thorhauge",
AmmoType = 0,
OutfitToken = -947477428,
SpecialSituation = 0,
WeaponToken = 1575676105
},
new()
{
Name = "Francois Munguia",
AmmoType = 0,
OutfitToken = -947477428,
SpecialSituation = 0,
WeaponToken = 0
}
}
},
Restrictions = new RestrictionsWrapper
{
Restrictions = new List<ERestrictionType>
{
ERestrictionType.NoWitnesses,
ERestrictionType.PerfectShooter
}
},
ExitId = 2,
Difficulty = 2,
LevelIndex = 0,
CheckpointIndex = 40,
Dislikes = 0,
Likes = 0,
Plays = 0,
StartingOutfitToken = -947477428,
StartingWeaponToken = 1575676105,
UserScore = 241953
};
private readonly Contract _mockedContractWithCompetition = new()
{
Id = 2,
DisplayId = "FakeContract48",
UserId = "76561198161220058",
UserName = "Wingz of Death",
Title = "Kill Diana",
Description = "Lol, joke.",
CompetitionLeader = "76561198161220058",
CompetitionHighestScore = 101,
HighestScoringFriendName = "76561198161220058",
HighestScoringFriendScore = 101,
Targets = new TargetsWrapper
{
Targets = new List<Target>
{
new()
{
Name = "David Thorhauge",
AmmoType = 0,
OutfitToken = -947477428,
SpecialSituation = 0,
WeaponToken = 1575676105
},
new()
{
Name = "Francois Munguia",
AmmoType = 0,
OutfitToken = -947477428,
SpecialSituation = 0,
WeaponToken = 0
}
}
},
Restrictions = new RestrictionsWrapper
{
Restrictions = new List<ERestrictionType>
{
ERestrictionType.NoWitnesses,
ERestrictionType.PerfectShooter
}
},
Competition = new CompetitionWrapper
{
Competition = new List<Competition>
{
new()
{
Id = 1,
AllowInvites = true,
DaysRemaining = 3,
CompetitionCreator = "76561198161220058",
EndTimeUTC = DateTime.UtcNow.AddDays(3)
}
}
},
ExitId = 2,
Difficulty = 2,
LevelIndex = 0,
CheckpointIndex = 40,
Dislikes = 0,
Likes = 0,
Plays = 0,
StartingOutfitToken = -947477428,
StartingWeaponToken = 1575676105,
UserScore = 241953
};
private readonly IMetadataService _metadataService;
public HitmanController(
ISimpleLogger simpleLogger,
IMetadataServiceForHitman metadataService
)
: base(simpleLogger)
{
_metadataService = metadataService;
}
public static List<EdmFunctionImport> GetEdmFunctionImports()
{
return new List<EdmFunctionImport>
{
_updateDLCInfo,
_getRichestAverageScores,
_reportContract,
_getScoreComparison,
_updateUserProfileLevelProgression,
_queueAddContract,
_searchForContracts2,
_getUserWallet,
_updateUserProfileSpecialRatings,
_uploadContract,
_sendTemplatedMessage,
_getMessages,
_updateContractLikeDislikes,
_queueRemoveContract,
_mergeUserTokens,
_executeWalletTransaction,
_getUserOverviewData,
_markContractAsPlayed,
_inviteToCompetition,
_createCompetition,
_getAverageScores,
_getDeadliestScores,
_updateUserInfo,
_getScores,
_getFeaturedContract,
_setMessageReadStatus,
_getRichestScores,
_getPopularAverageScores,
_updateUserProfileChallenges,
_updateUserProfileGameStats,
_putScore,
_getDeadliestAverageScores,
_getPopularScores,
_getNewMessageCount
};
}
public static List<Type> GetEdmEntityTypes()
{
return new List<Type>
{
typeof(Contract),
typeof(GetUserOverviewData),
typeof(Message),
typeof(ScoreComparison),
typeof(ScoreEntry),
typeof(UserTokenData)
};
}
}
}

View File

@ -0,0 +1,15 @@
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
public partial class HitmanController
{
[HttpPost]
[Route("AddMetrics")]
public IActionResult AddMetrics([FromBody] JsonDocument json)
{
return Ok();
}
}
}

View File

@ -0,0 +1,45 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractManager::UpdateCompetition @ 006E50B0
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _createCompetition = new()
{
Name = "CreateCompetition",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "contractId",
Type = EdmTypes.String
},
new()
{
Name = "competitionLength",
Type = EdmTypes.String
},
new()
{
Name = "allowInvites",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("CreateCompetition")]
public IActionResult CreateCompetition()
{
return Ok();
}
}
}

View File

@ -0,0 +1,57 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractManager::ExecuteWalletTransactionAsync @ 00684EF0
* Callback: ZContractManager::ExecuteWalletTransactionAsyncCallback @ 005E88A0
* ReturnType: ZOServiceOperationResult
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _executeWalletTransaction = new()
{
Name = "ExecuteWalletTransaction",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new ()
{
Name = "amount",
Type = EdmTypes.String
},
new ()
{
Name = "userId",
Type = EdmTypes.String
},
new ()
{
Name = "tokenId",
Type = EdmTypes.String
},
new ()
{
Name = "subId",
Type = EdmTypes.String
},
new ()
{
Name = "level",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("ExecuteWalletTransaction")]
public IActionResult ExecuteWalletTransaction()
{
//NOTE: Appears to be the new WalletAmount left
return JsonOperationValueResponse(101);
}
}
}

View File

@ -0,0 +1,46 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZLeaderboard::GetAverageScores @ 0073C100
* Callback: ZLeaderboard::GetAverageScoresCallback @ 00760350
* ReturnType: ZOServiceOperationResult
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getAverageScores = new()
{
Name = "GetAverageScores",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({EdmTypes.String})",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "leaderboardtype",
Type = EdmTypes.String
},
new()
{
Name = "leaderboardid",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetAverageScores")]
public IActionResult GetAverageScores()
{
return JsonOperationListResponse(_mockedGetAverageScoresResponse);
}
}
}

View File

@ -0,0 +1,36 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZLeaderboard::GetSpecialAverageDeadliest @ 00919C70
* Callback: ZLeaderboard::GetAverageScoresCallback @ 00760350
* ReturnType: ZOServiceOperationResult
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getDeadliestAverageScores = new()
{
Name = "GetDeadliestAverageScores",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({EdmTypes.String})",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetDeadliestAverageScores")]
public IActionResult GetDeadliestAverageScores()
{
return JsonOperationListResponse(_mockedGetAverageScoresResponse);
}
}
}

View File

@ -0,0 +1,51 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZLeaderboard::GetSpecialDeadliest @ 00791C00
* Callback: ZLeaderboard::GetScoresCallback @ 007FD3D0
* ReturnType: ZOFeed
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getDeadliestScores = new()
{
Name = "GetDeadliestScores",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({SchemaNamespace}.ScoreEntry)",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "filter",
Type = EdmTypes.String
},
new()
{
Name = "startindex",
Type = EdmTypes.String
},
new()
{
Name = "range",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetDeadliestScores")]
public IActionResult GetDeadliestScores()
{
return JsonFeedResponse(_mockedGetScoresResponse);
}
}
}

View File

@ -0,0 +1,41 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractManager::Start @ 806E20
* Callback: ZContractManager::RetrieveFeaturedContractCallback @ 964FE0
* ReturnType: ZOEntry
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getFeaturedContract = new()
{
Name = "GetFeaturedContract",
HttpMethod = HttpMethods.GET,
ReturnType = $"{SchemaNamespace}.Contract",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "levelindex",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetFeaturedContract")]
public IActionResult GetFeaturedContract()
{
return JsonEntryResponse(_mockedContractWithCompetition);
}
}
}

View File

@ -0,0 +1,81 @@
using HM5.Server.Enums;
using HM5.Server.Models;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke:
* - ZMessageManager::GetOnlineMessages @ 008D5730
* - ZMessageManager::GetNewOnlineMessages @ 005EE400
* Callback:
* - ZMessageManager::OnGetMessagesComplete @ 9D8CF0
* - ZMessageManager::OnGetNewMessagesComplete @ 00961A30
* ReturnType: ZOFeed
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getMessages = new()
{
Name = "GetMessages",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({SchemaNamespace}.Message)",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userId",
Type = EdmTypes.String
},
new()
{
Name = "tabgroup",
Type = EdmTypes.String
},
new()
{
Name = "languageId",
Type = EdmTypes.String
},
new()
{
Name = "skip",
Type = EdmTypes.String
},
new()
{
Name = "limit",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetMessages")]
public IActionResult GetMessages()
{
return JsonFeedResponse(new List<Message>
{
new()
{
Id = 5000,
FromId = "76561198161220058",
IsRead = false,
TextTemplateId = 0,
TemplateData = "Heya!",
TimestampUTC = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
},
new()
{
Id = 5001,
FromId = "76561198161220058",
IsRead = true,
TextTemplateId = 0,
TemplateData = "Heya {userid}!",
TimestampUTC = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
}
});
}
}
}

View File

@ -0,0 +1,36 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZMessageManager::Update @ 0099DFA0
* Callback: ZMessageManager::OnGetNewMessageCountComplete @ 008847F0
* ReturnType: ZOServiceOperationResult
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getNewMessageCount = new()
{
Name = "GetNewMessageCount",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userId",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetNewMessageCount")]
public IActionResult GetNewMessageCount()
{
return JsonOperationValueResponse(10);
}
}
}

View File

@ -0,0 +1,36 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZLeaderboard::GetSpecialAveragePopular @ 00895B20
* Callback: ZLeaderboard::GetAverageScoresCallback @ 00760350
* ReturnType: ZOServiceOperationResult
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getPopularAverageScores = new()
{
Name = "GetPopularAverageScores",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({EdmTypes.String})",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetPopularAverageScores")]
public IActionResult GetPopularAverageScores()
{
return JsonOperationListResponse(_mockedGetAverageScoresResponse);
}
}
}

View File

@ -0,0 +1,51 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZLeaderboard::GetSpecialPopular @ 0095F6A0
* Callback: ZLeaderboard::GetScoresCallback @ 007FD3D0
* ReturnType: ZOFeed
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getPopularScores = new()
{
Name = "GetPopularScores",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({SchemaNamespace}.ScoreEntry)",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "filter",
Type = EdmTypes.String
},
new()
{
Name = "startindex",
Type = EdmTypes.String
},
new()
{
Name = "range",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetPopularScores")]
public IActionResult GetPopularScores()
{
return JsonFeedResponse(_mockedGetScoresResponse);
}
}
}

View File

@ -0,0 +1,36 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZLeaderboard::GetSpecialAverageRichest @ 0044D220
* Callback: ZLeaderboard::GetAverageScoresCallback @ 00760350
* ReturnType: ZOServiceOperationResult
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getRichestAverageScores = new()
{
Name = "GetRichestAverageScores",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({EdmTypes.String})",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetRichestAverageScores")]
public IActionResult GetRichestAverageScores()
{
return JsonOperationListResponse(_mockedGetAverageScoresResponse);
}
}
}

View File

@ -0,0 +1,51 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZLeaderboard::GetSpecialRichest @ 00868C30
* Callback: ZLeaderboard::GetScoresCallback @ 007FD3D0
* ReturnType: ZOFeed
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getRichestScores = new()
{
Name = "GetRichestScores",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({SchemaNamespace}.ScoreEntry)",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "filter",
Type = EdmTypes.String
},
new()
{
Name = "startindex",
Type = EdmTypes.String
},
new()
{
Name = "range",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetRichestScores")]
public IActionResult GetRichestScores()
{
return JsonFeedResponse(_mockedGetScoresResponse);
}
}
}

View File

@ -0,0 +1,54 @@
using HM5.Server.Enums;
using HM5.Server.Models;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZLeaderboard::GetScoreComparison @ 00458D00
* Callback: ZLeaderboard::GetScoreComparisonCallback @ 00624360
* ReturnType: ZOEntry
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getScoreComparison = new()
{
Name = "GetScoreComparison",
HttpMethod = HttpMethods.GET,
ReturnType = $"{SchemaNamespace}.ScoreComparison",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "leaderboardtype",
Type = EdmTypes.String
},
new()
{
Name = "leaderboardid",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetScoreComparison")]
public IActionResult GetScoreComparison()
{
return JsonEntryResponse(new ScoreComparison
{
//NOTE: Has to be a SteamID
FriendName = "76561198161220058",
FriendScore = 1337,
CountryAverage = 101,
WorldAverage = 101
});
}
}
}

View File

@ -0,0 +1,61 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZLeaderboard::GetScores @ 007FA160
* Callback: ZLeaderboard::GetScoresCallback @ 007FD3D0
* ReturnType: ZOFeed
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getScores = new()
{
Name = "GetScores",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({SchemaNamespace}.ScoreEntry)",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "leaderboardtype",
Type = EdmTypes.String
},
new()
{
Name = "leaderboardid",
Type = EdmTypes.String
},
new()
{
Name = "filter",
Type = EdmTypes.String
},
new()
{
Name = "startindex",
Type = EdmTypes.String
},
new()
{
Name = "range",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetScores")]
public IActionResult GetScores()
{
return JsonFeedResponse(_mockedGetScoresResponse);
}
}
}

View File

@ -0,0 +1,51 @@
using HM5.Server.Enums;
using HM5.Server.Models;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractManager::RetrieveUserOverviewData @ 006DF920
* Callback: ZContractManager::RetrieveUserOverviewDataCallback @ 00554640
* ReturnType: ZOEntry
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getUserOverviewData = new()
{
Name = "GetUserOverviewData",
HttpMethod = HttpMethods.GET,
ReturnType = $"{SchemaNamespace}.GetUserOverviewData",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetUserOverviewData")]
public IActionResult GetUserOverviewData()
{
return JsonEntryResponse(new GetUserOverviewData
{
ContractPlays = 1337,
CompetitionPlays = 1337,
ContractsCreated = 1337,
ContractsCreatedLikes = 1337,
DeadliestAverage = 1337,
DeadliestRank = 1337,
PopularAverage = 1337,
PopularRank = 1337,
RichestAverage = 1337,
RichestRank = 1337,
TrophiesEarned = 1337,
WalletAmount = 1337
});
}
}
}

View File

@ -0,0 +1,36 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractManager::GetUserWallet @ 00525090
* Callback: ZContractManager::GetUserWalletCallback @ 008BBBC0
* ReturnType: ZOServiceOperationResult
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _getUserWallet = new()
{
Name = "GetUserWallet",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userId",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetUserWallet")]
public IActionResult GetUserWallet()
{
return JsonOperationValueResponse(1_000_000);
}
}
}

View File

@ -0,0 +1,45 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractManager::UpdateCompetition @ 006E50B0
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _inviteToCompetition = new()
{
Name = "InviteToCompetition",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "fromId",
Type = EdmTypes.String
},
new()
{
Name = "participants",
Type = EdmTypes.String
},
new()
{
Name = "competitionId",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("InviteToCompetition")]
public IActionResult InviteToCompetition()
{
return Ok();
}
}
}

View File

@ -0,0 +1,40 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractPlayer::Activate @ 006DFD10
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _markContractAsPlayed = new()
{
Name = "MarkContractAsPlayed",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userId",
Type = EdmTypes.String
},
new()
{
Name = "contractId",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("MarkContractAsPlayed")]
public IActionResult MarkContractAsPlayed()
{
return Ok();
}
}
}

View File

@ -0,0 +1,43 @@
using HM5.Server.Enums;
using HM5.Server.Models;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractManager::MergeUserTokensAsync @ 00683F60
* Callback: ZContractManager::MergeUserTokensAsyncCallback @ 0089A170
* ReturnType: ZOFeed
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _mergeUserTokens = new()
{
Name = "MergeUserTokens",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({SchemaNamespace}.UserTokenData)",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userId",
Type = EdmTypes.String
},
new()
{
Name = "tokenData",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("MergeUserTokens")]
public IActionResult MergeUserTokens()
{
//NOTE: tokenData needs further parsing
return JsonFeedResponse(new List<UserTokenData>());
}
}
}

View File

@ -0,0 +1,18 @@
using HM5.Server.Models;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
public partial class HitmanController
{
[HttpGet]
[Route("os_getStatus")]
public IActionResult GetStatus()
{
return JsonGenericResponse(new OSGetStatus
{
ClientIP = "127.0.0.1"
});
}
}
}

View File

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
public partial class HitmanController
{
[HttpGet]
[Route("$os_metadata")]
public IActionResult Metadata()
{
return JsonGenericResponse(_metadataService.GetMetadata());
}
}
}

View File

@ -0,0 +1,57 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZLeaderboard::UploadScore @ 00915670
* Callback: ZLeaderboard::UploadScoreCallback @ 004630A0
* ReturnType: ZOServiceOperationResult
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _putScore = new()
{
Name = "PutScore",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "leaderboardtype",
Type = EdmTypes.String
},
new()
{
Name = "leaderboardid",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
},
new()
{
Name = "score",
Type = EdmTypes.String
},
new()
{
Name = "rating",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("PutScore")]
public IActionResult PutScore()
{
//NOTE: Appears to be the difference between your new and last (personal best) score
return JsonOperationValueResponse(1337);
}
}
}

View File

@ -0,0 +1,40 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractManager::AddContractToQueue @ 004DCF90
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _queueAddContract = new()
{
Name = "QueueAddContract",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "contractid",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("QueueAddContract")]
public IActionResult QueueAddContract()
{
return Ok();
}
}
}

View File

@ -0,0 +1,40 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractManager::ChangeLevel @ 00646AD0
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _queueRemoveContract = new()
{
Name = "QueueRemoveContract",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userid",
Type = EdmTypes.String
},
new()
{
Name = "contractid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("QueueRemoveContract")]
public IActionResult QueueRemoveContract()
{
return Ok();
}
}
}

View File

@ -0,0 +1,47 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke:
* - ZContractManager::ReportContract @ 0044DDF0
* - ZContractManager::ReportContract @ 0049E220
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _reportContract = new()
{
Name = "ReportContract",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userid",
Type = EdmTypes.String
},
new()
{
Name = "contractid",
Type = EdmTypes.String
},
new()
{
Name = "reason",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("ReportContract")]
public IActionResult ReportContract()
{
return Ok();
}
}
}

View File

@ -0,0 +1,91 @@
using HM5.Server.Enums;
using HM5.Server.Models;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractSearchEngine::Retrieve @ 0051C9F0
* Callback: ZContractSearchEngine::RetrieveCallback @ 5DF560
* ReturnType: ZOFeed
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _searchForContracts2 = new()
{
Name = "SearchForContracts2",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({SchemaNamespace}.Contract)",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "view",
Type = EdmTypes.String
},
new()
{
Name = "sort",
Type = EdmTypes.String
},
new()
{
Name = "levelindex",
Type = EdmTypes.String
},
new()
{
Name = "checkpointid",
Type = EdmTypes.String
},
new()
{
Name = "categoryid",
Type = EdmTypes.String
},
new()
{
Name = "difficulty",
Type = EdmTypes.String
},
new()
{
Name = "contractid",
Type = EdmTypes.String
},
new()
{
Name = "contractname",
Type = EdmTypes.String
},
new()
{
Name = "startindex",
Type = EdmTypes.String
},
new()
{
Name = "range",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("SearchForContracts2")]
public IActionResult SearchForContracts2()
{
return JsonFeedResponse(new List<Contract>
{
_mockedContractWithoutCompetition,
_mockedContractWithCompetition
});
}
}
}

View File

@ -0,0 +1,65 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke:
* - ZMessageManager::SendMessageToSelf @ 005B5A20
* - ZMessageManager::SendMessageToUsersFriends @ 0073AB00
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _sendTemplatedMessage = new()
{
Name = "SendTemplatedMessage",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "fromId",
Type = EdmTypes.String
},
new()
{
Name = "toUserId",
Type = EdmTypes.String
},
new()
{
Name = "tabgroup",
Type = EdmTypes.String
},
new()
{
Name = "category",
Type = EdmTypes.String
},
new()
{
Name = "templateId",
Type = EdmTypes.String
},
new()
{
Name = "data",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("SendTemplatedMessage")]
public IActionResult SendTemplatedMessage()
{
//NOTE: toUserId will be "----Friends----" if it's called from SendMessageToUsersFriends
//NOTE: tabgroup will be "1" if it's called from SendMessageToSelf
//NOTE: data might contain data that needs additional parsing
return Ok();
}
}
}

View File

@ -0,0 +1,41 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZMessageManager::MarkMessageAsRead @ 0083A8A0
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _setMessageReadStatus = new()
{
Name = "SetMessageReadStatus",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "messageId",
Type = EdmTypes.String
},
new()
{
Name = "isRead",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("SetMessageReadStatus")]
public IActionResult SetMessageReadStatus()
{
//NOTE: isRead will always be set to "true"
return Ok();
}
}
}

View File

@ -0,0 +1,50 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractManager::UpdateLikesDislikes @ 0060FC20
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _updateContractLikeDislikes = new()
{
Name = "UpdateContractLikeDislikes",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "fromUserId",
Type = EdmTypes.String
},
new()
{
Name = "contractId",
Type = EdmTypes.String
},
new()
{
Name = "likesIncrement",
Type = EdmTypes.String
},
new()
{
Name = "dislikesIncrement",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("UpdateContractLikeDislikes")]
public IActionResult UpdateContractLikeDislikes()
{
return Ok();
}
}
}

View File

@ -0,0 +1,40 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZOnlineManager::UploadDLCToken @ 0042AA0
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _updateDLCInfo = new()
{
Name = "UpdateDLCInfo",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "dlctokens",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("UpdateDLCInfo")]
public IActionResult UpdateDLCInfo()
{
return Ok();
}
}
}

View File

@ -0,0 +1,51 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZOnlineManager::UploadProfile @ 007ADF30
* Callback: ZOnlineManager::OnUpdateUserProfile @ 006FB440
* ReturnType: None, but 200 - OK.
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _updateUserInfo = new()
{
Name = "UpdateUserInfo",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userid",
Type = EdmTypes.String
},
new()
{
Name = "displayName",
Type = EdmTypes.String
},
new()
{
Name = "country",
Type = EdmTypes.String
},
new()
{
Name = "friends",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("UpdateUserInfo")]
public IActionResult UpdateUserInfo()
{
return Ok();
}
}
}

View File

@ -0,0 +1,41 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZOnlineManager::UploadProfileChallenges @ 008DE580
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _updateUserProfileChallenges = new()
{
Name = "UpdateUserProfileChallenges",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userid",
Type = EdmTypes.String
},
new()
{
Name = "data",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("UpdateUserProfileChallenges")]
public IActionResult UpdateUserProfileChallenges()
{
//NOTE: data contains a data that needs to be parsed
return Ok();
}
}
}

View File

@ -0,0 +1,41 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZOnlineManager::UploadProfileGameStats @ 0090A2D0
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _updateUserProfileGameStats = new()
{
Name = "UpdateUserProfileGameStats",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userid",
Type = EdmTypes.String
},
new()
{
Name = "data",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("UpdateUserProfileGameStats")]
public IActionResult UpdateUserProfileGameStats()
{
//NOTE: data contains a data that needs to be parsed
return Ok();
}
}
}

View File

@ -0,0 +1,41 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZOnlineManager::UploadProfileLevelProgression @ 004613F0
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _updateUserProfileLevelProgression = new()
{
Name = "UpdateUserProfileLevelProgression",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userid",
Type = EdmTypes.String
},
new()
{
Name = "data",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("UpdateUserProfileLevelProgression")]
public IActionResult UpdateUserProfileLevelProgression()
{
//NOTE: data contains a data that needs to be parsed
return Ok();
}
}
}

View File

@ -0,0 +1,41 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZOnlineManager::UploadProfileSpecialRatings @ 0054AB20
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _updateUserProfileSpecialRatings = new()
{
Name = "UpdateUserProfileSpecialRatings",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userid",
Type = EdmTypes.String
},
new()
{
Name = "data",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("UpdateUserProfileSpecialRatings")]
public IActionResult UpdateUserProfileSpecialRatings()
{
//NOTE: data contains a data that needs to be parsed
return Ok();
}
}
}

View File

@ -0,0 +1,110 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Hitman
{
/**
* Invoke: ZContractCreator::SaveContract @ 005A7150
* Callback: None
*/
public partial class HitmanController
{
private static readonly EdmFunctionImport _uploadContract = new()
{
Name = "UploadContract",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "levelIndex",
Type = EdmTypes.String
},
new()
{
Name = "checkpointIndex",
Type = EdmTypes.String
},
new()
{
Name = "difficulty",
Type = EdmTypes.String
},
new()
{
Name = "exitId",
Type = EdmTypes.String
},
new()
{
Name = "userId",
Type = EdmTypes.String
},
new()
{
Name = "title",
Type = EdmTypes.String
},
new()
{
Name = "description",
Type = EdmTypes.String
},
new()
{
Name = "score",
Type = EdmTypes.String
},
new()
{
Name = "startingweapontoken",
Type = EdmTypes.String
},
new()
{
Name = "startingoutfittoken",
Type = EdmTypes.String
},
new()
{
Name = "competitionParticipants",
Type = EdmTypes.String
},
new()
{
Name = "competitionAllowInvites",
Type = EdmTypes.String
},
new()
{
Name = "competitionDuration",
Type = EdmTypes.String
},
new()
{
Name = "targetsJson",
Type = EdmTypes.String
},
new()
{
Name = "restrictionsJson",
Type = EdmTypes.String
},
new()
{
Name = "metacategoriesJson",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("UploadContract")]
public IActionResult UploadContract()
{
return Ok();
}
}
}

View File

@ -0,0 +1,76 @@
using HM5.Server.Interfaces;
using HM5.Server.Models;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Sniper
{
[Route("sniper")]
public partial class SniperController : BaseController
{
private readonly List<SniperScoreEntry> _mockedGetScoresResponse = new()
{
new SniperScoreEntry
{
UserId = "76561197989140534",
Rank = 1,
Score = 1989
},
new SniperScoreEntry
{
UserId = "76561198025604927",
Rank = 2,
Score = 1989
},
new SniperScoreEntry
{
UserId = "76561198215015615",
Rank = 3,
Score = 1989
},
new SniperScoreEntry
{
UserId = "76561198161220058",
Rank = 4,
Score = 1989
}
};
public const string SchemaNamespace = "Sniper";
private readonly IMetadataService _metadataService;
public SniperController(
ISimpleLogger simpleLogger,
IMetadataServiceForSniper metadataService
)
: base(simpleLogger)
{
_metadataService = metadataService;
}
public static List<EdmFunctionImport> GetEdmFunctionImports()
{
return new List<EdmFunctionImport>
{
_sendTemplatedMessage,
_setMessageReadStatus,
_getMessages,
_getNewMessageCount,
_updateUserProfile,
_putScore,
_getScores,
_getPerformanceIndexAll
};
}
public static List<Type> GetEdmEntityTypes()
{
return new List<Type>
{
typeof(SniperScoreEntry),
typeof(SniperMessage)
};
}
}
}

View File

@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
namespace HM5.Server.Controllers.Sniper
{
public partial class SniperController
{
[HttpPost]
[Route("AddMetrics")]
public IActionResult AddMetrics([FromBody] JsonDocument json)
{
return Ok();
}
}
}

View File

@ -0,0 +1,81 @@
using HM5.Server.Enums;
using HM5.Server.Models;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Sniper
{
/**
* Invoke:
* - ZMessageManager::GetOnlineMessages @ 00800AA00
* - ZMessageManager::GetNewOnlineMessages @ 00800E60
* Callback:
* - ZMessageManager::OnGetMessagesComplete @ 00800780
* - ZMessageManager::OnGetNewMessagesComplete @ 008009F0
* ReturnType: ZOFeed
*/
public partial class SniperController
{
private static readonly EdmFunctionImport _getMessages = new()
{
Name = "GetMessages",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({SchemaNamespace}.Message)",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userId",
Type = EdmTypes.String
},
new()
{
Name = "tabgroup",
Type = EdmTypes.String
},
new()
{
Name = "languageId",
Type = EdmTypes.String
},
new()
{
Name = "skip",
Type = EdmTypes.String
},
new()
{
Name = "limit",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetMessages")]
public IActionResult GetMessages()
{
return JsonFeedResponse(new List<SniperMessage>
{
new()
{
Id = 5000,
FromId = "76561198161220058",
Category = 1,
TextTemplateId = 0,
TemplateData = "Heya!",
TimestampUTC = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
},
new()
{
Id = 5001,
FromId = "76561198161220058",
Category = 1,
TextTemplateId = 0,
TemplateData = "Heya {userid}!",
TimestampUTC = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
}
});
}
}
}

View File

@ -0,0 +1,37 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Sniper
{
/**
* Invoke: ZMessageManager::Update @ 00801320
* Callback: ZMessageManager::OnGetNewMessageCountComplete @ 008011F0
* ReturnType: ZOServiceOperationResult
*/
public partial class SniperController
{
private static readonly EdmFunctionImport _getNewMessageCount = new()
{
Name = "GetNewMessageCount",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userId",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetNewMessageCount")]
public IActionResult GetNewMessageCount()
{
//NOTE: GetNewMessageCount doesn't care for the name of the Key
return JsonOperationValueResponse(10);
}
}
}

View File

@ -0,0 +1,50 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Sniper
{
/**
* Invoke: ZUnknown::GetPerformanceIndexAll @ 0080E890
* Callback: ZUnknown::GetPerformanceIndexAllCallback @ 0080E4B0
* ReturnType: ZOServiceOperationResult
*/
public partial class SniperController
{
private static readonly EdmFunctionImport _getPerformanceIndexAll = new()
{
Name = "GetPerformanceIndexAll",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "leaderboardid",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetPerformanceIndexAll")]
public IActionResult GetPerformanceIndexAll()
{
//NOTE: Different performance percentages are at: 0, 0.55, 0.65, 0.75 and 1
return JsonOperationListResponse(new List<float>
{
1, //Global
0.65f, //Global percentage
1, //National
0.55f, //National percentage
1, //Friends
0.75f //Friends percentage
});
}
}
}

View File

@ -0,0 +1,57 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Sniper
{
/**
* Invoke: ZLeaderboard::GetScores @ 0080E630
* Callback: ZLeaderboard::GetScoresCallback @ 0080DF80
* ReturnType: ZOFeed
*/
public partial class SniperController
{
private static readonly EdmFunctionImport _getScores = new()
{
Name = "GetScores",
HttpMethod = HttpMethods.GET,
ReturnType = $"Collection({SchemaNamespace}.ScoreEntry)",
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "leaderboardid",
Type = EdmTypes.String
},
new()
{
Name = "filter",
Type = EdmTypes.String
},
new()
{
Name = "startindex",
Type = EdmTypes.String
},
new()
{
Name = "range",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("GetScores")]
public IActionResult GetScores()
{
//NOTE: leaderboardid is always 0
return JsonFeedResponse(_mockedGetScoresResponse);
}
}
}

View File

@ -0,0 +1,18 @@
using HM5.Server.Models;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Sniper
{
public partial class SniperController
{
[HttpGet]
[Route("os_getStatus")]
public IActionResult GetStatus()
{
return JsonGenericResponse(new OSGetStatus
{
ClientIP = "127.0.0.1"
});
}
}
}

View File

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Sniper
{
public partial class SniperController
{
[HttpGet]
[Route("$os_metadata")]
public IActionResult Metadata()
{
return JsonGenericResponse(_metadataService.GetMetadata());
}
}
}

View File

@ -0,0 +1,45 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Sniper
{
/**
* Invoke: ZLeaderboard::UploadScore @ 0080DE00
* Callback: None
*/
public partial class SniperController
{
private static readonly EdmFunctionImport _putScore = new()
{
Name = "PutScore",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "leaderboardid",
Type = EdmTypes.String
},
new()
{
Name = "userid",
Type = EdmTypes.String
},
new()
{
Name = "score",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("PutScore")]
public IActionResult PutScore()
{
return Ok();
}
}
}

View File

@ -0,0 +1,65 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Sniper
{
/**
* Invoke:
* - ZMessageManager::SendMessageToUsersFriends @ 007FF900
* - ZMessageManager::SendMessageToSelf @ 007FFCC0
* Callback: None
*/
public partial class SniperController
{
private static readonly EdmFunctionImport _sendTemplatedMessage = new()
{
Name = "SendTemplatedMessage",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "fromId",
Type = EdmTypes.String
},
new()
{
Name = "toUserId",
Type = EdmTypes.String
},
new()
{
Name = "tabgroup",
Type = EdmTypes.String
},
new()
{
Name = "category",
Type = EdmTypes.String
},
new()
{
Name = "templateId",
Type = EdmTypes.String
},
new()
{
Name = "data",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("SendTemplatedMessage")]
public IActionResult SendTemplatedMessage()
{
//NOTE: toUserId will be "----Friends----" if it's called from SendMessageToUsersFriends
//NOTE: tabgroup will be "1" if it's called from SendMessageToSelf
//NOTE: data might contain data that needs additional parsing
return Ok();
}
}
}

View File

@ -0,0 +1,41 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Sniper
{
/**
* Invoke: ZMessageManager::MarkMessageAsRead @ 00800010
* Callback: None
*/
public partial class SniperController
{
private static readonly EdmFunctionImport _setMessageReadStatus = new()
{
Name = "SetMessageReadStatus",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "messageId",
Type = EdmTypes.String
},
new()
{
Name = "isRead",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("SetMessageReadStatus")]
public IActionResult SetMessageReadStatus()
{
//NOTE: isRead will always be set to "true"
return Ok();
}
}
}

View File

@ -0,0 +1,45 @@
using HM5.Server.Enums;
using HM5.Server.Models.Base;
using Microsoft.AspNetCore.Mvc;
namespace HM5.Server.Controllers.Sniper
{
/**
* Invoke: ZOnlineManager::UploadProfile @ 0080DB10
* Callback: None
*/
public partial class SniperController
{
private static readonly EdmFunctionImport _updateUserProfile = new()
{
Name = "UpdateUserProfile",
HttpMethod = HttpMethods.GET,
ReturnType = null,
Parameters = new List<SFunctionParameter>
{
new()
{
Name = "userid",
Type = EdmTypes.String
},
new()
{
Name = "country",
Type = EdmTypes.String
},
new()
{
Name = "friends",
Type = EdmTypes.String
}
}
};
[HttpGet]
[Route("UpdateUserProfile")]
public IActionResult UpdateUserProfile()
{
return Ok();
}
}
}

View File

@ -0,0 +1,12 @@
namespace HM5.Server.Enums
{
public enum ERestrictionType
{
TargetOnly = 0,
SuitOnly = 1,
PerfectShooter = 2,
EraseTraces = 3,
NoWitnesses = 4,
Invalid = 255
}
}

View File

@ -0,0 +1,23 @@
namespace HM5.Server.Enums
{
public static class EdmTypes
{
public const string Null = "Null"; //0
public const string Binary = "Edm.Binary"; //1
public const string Boolean = "Edm.Boolean"; //2
public const string Byte = "Edm.Byte"; //3
public const string DateTime = "Edm.DateTime"; //4
public const string DateTimeOffset = "Edm.DateTimeOffset"; //5
public const string Decimal = "Edm.Decimal"; //6
public const string Double = "Edm.Double"; //7
public const string Guid = "Edm.Guid"; //8
public const string Int16 = "Edm.Int16"; //9
public const string Int32 = "Edm.Int32"; //10
public const string Int64 = "Edm.Int64"; //11
public const string SByte = "Edm.SByte"; //12
public const string Single = "Edm.Single"; //13
public const string String = "Edm.String"; //14
public const string Time = "Edm.Time"; //15
public const string ComplexType = "ComplexType"; //16
}
}

View File

@ -0,0 +1,8 @@
namespace HM5.Server.Enums
{
public static class HttpMethods
{
public const string GET = "GET";
public const string POST = "POST";
}
}

View File

@ -0,0 +1,9 @@
namespace HM5.Server.Enums
{
public static class Multiplicity
{
public const string Zero = "0";
public const string One = "1";
public const string Many = "*";
}
}

View File

@ -0,0 +1,8 @@
namespace HM5.Server.Enums
{
public static class Nullable
{
public const string True = "true";
public const string False = "false";
}
}

View File

@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace HM5.Server.Extensions
{
public static class ModelBindingContextExtensions
{
public static string NormalizeString(this ModelBindingContext bindingContext)
{
return bindingContext.ValueProvider
.GetValue(bindingContext.ModelName).Values
.FirstOrDefault()?
.Trim('\'');
}
}
}

View File

@ -0,0 +1 @@
global using HttpMethods = HM5.Server.Enums.HttpMethods;

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="Logs\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,7 @@
namespace HM5.Server.Interfaces
{
public interface IEdmEntity
{
//Do nothing
}
}

View File

@ -0,0 +1,9 @@
using HM5.Server.Models;
namespace HM5.Server.Interfaces
{
public interface IMetadataService
{
OSMetadata GetMetadata();
}
}

View File

@ -0,0 +1,7 @@
namespace HM5.Server.Interfaces
{
public interface IMetadataServiceForHitman : IMetadataService
{
//Do nothing
}
}

View File

@ -0,0 +1,7 @@
namespace HM5.Server.Interfaces
{
public interface IMetadataServiceForSniper : IMetadataService
{
//Do nothing
}
}

View File

@ -0,0 +1,7 @@
namespace HM5.Server.Interfaces
{
public interface ISimpleLogger
{
void WriteLine(string message);
}
}

View File

@ -0,0 +1,19 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace HM5.Server.Json
{
public class AnyToJsonStringConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
//NOTE: options is purposely not passed on to the Serialize method, since the in-game JSON parser used to Deserialize this actually works fine with the default options.
writer.WriteStringValue(JsonSerializer.Serialize(value));
}
}
}

View File

@ -0,0 +1,19 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Nullable = HM5.Server.Enums.Nullable;
namespace HM5.Server.Json
{
public class BooleanToStringConverter : JsonConverter<bool>
{
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
{
writer.WriteStringValue(value ? Nullable.True : Nullable.False);
}
}
}

View File

@ -0,0 +1,19 @@
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace HM5.Server.Json
{
public class FloatToStringConverter : JsonConverter<float>
{
public override float Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, float value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture));
}
}
}

View File

@ -0,0 +1,18 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace HM5.Server.Json
{
public class IntegerToStringConverter : JsonConverter<int>
{
public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
}

View File

@ -0,0 +1,8 @@
namespace HM5.Server.Models.Base
{
public class EdmAssociation
{
public string Name { get; set; }
public List<EdmEndRole> Ends { get; set; }
}
}

View File

@ -0,0 +1,11 @@
namespace HM5.Server.Models.Base
{
public class EdmClientConfiguration
{
public int MetricsThreshold { get; set; }
public int MetricsPriorityThreshold { get; set; }
public bool UsageTrackingEnabled { get; set; }
public int UsageTrackingSamplingInterval { get; set; }
public int UsageTrackingMetricsInterval { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace HM5.Server.Models.Base
{
public class EdmComplexType
{
public string Name { get; set; }
public List<EdmProperty> Properties { get; set; }
}
}

View File

@ -0,0 +1,11 @@
namespace HM5.Server.Models.Base
{
public class EdmEndRole
{
public string Role { get; set; }
public string Type { get; set; }
//NOTE: Game converts Many to 2, default is 0.
public string Multiplicity { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace HM5.Server.Models.Base
{
public class EdmEntitySet
{
public string Name { get; set; }
public string EntityType { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More