Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Hagb committed Nov 21, 2023
0 parents commit 7f5b70e
Show file tree
Hide file tree
Showing 10 changed files with 359 additions and 0 deletions.
40 changes: 40 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Prerequisites
*.d

# Compiled Object files
*.slo
*.lo
*.o
*.obj

# Precompiled Headers
*.gch
*.pch

# Compiled Dynamic libraries
*.so
*.dylib
*.dll

# Fortran module files
*.mod
*.smod

# Compiled Static libraries
*.lai
*.la
*.a
*.lib

# Executables
*.exe
*.out
*.app

# IDE stuff
.idea
.vs

# Build directories
cmake-build-*
build
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "SokuLib"]
path = SokuLib
url = https://github.com/SokuDev/SokuLib
36 changes: 36 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.15)
cmake_policy(SET CMP0091 NEW)
set(PROJECT_NAME SaveRep)
project("${PROJECT_NAME}" C CXX)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/install")
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")

add_definitions(-DWINVER=0x0501 -D_WIN32_WINNT=0x0501)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++11-narrowing -Wno-microsoft-cast")
endif ()
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /Brepro")
SET(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /Brepro")
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /Brepro")

# SokuLib
add_subdirectory(SokuLib)

# Module
add_library(
"${PROJECT_NAME}"
MODULE
src/main.cpp
src/version.rc
)
target_compile_options("${PROJECT_NAME}" PRIVATE /Zi)
target_compile_definitions("${PROJECT_NAME}" PRIVATE DIRECTINPUT_VERSION=0x0800 CURL_STATICLIB _CRT_SECURE_NO_WARNINGS $<$<CONFIG:Debug>:_DEBUG>)
target_link_directories("${PROJECT_NAME}" PRIVATE lib)
target_link_libraries(
"${PROJECT_NAME}"
SokuLib
)
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 SokuDev

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# SaveRep mod for Touhou Hisoutensoku

This mod is to save replay when in one of the following situations:

- the user is p1 or p2 in network battle, and:
- p1 or p2 presses ESC to end the game, or
- the game ends before one of the players wins because of desync, or
- the connection is lost
- the user is spectating, and:
- the user presses ESC to stop spectating, or
- the connection is lost

## Build
Requires CMake, git and the VisualStudio compiler (MSVC).
Both git and cmake needs to be in the PATH environment variable.

All the following commands are to be run inside the visual studio 32bits compiler
command prompt (called `x86 Native Tools Command Prompt for VS 20XX` in the start menu), unless stated otherwise.

## Initialization
First go inside the folder you want the repository to be in.
In this example it will be C:\Users\PinkySmile\SokuProjects but remember to replace this
with the path for your machine. If you don't want to type the full path, you can drag and
drop the folder onto the console.

`cd C:\Users\PinkySmile\SokuProjects`

Now let's download the repository and initialize it for the first time
```
git clone https://github.com/Hagb/SaveRep
cd SaveRep
git submodule init
git submodule update
mkdir build
cd build
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Debug
```
Note that if you want to build in Release, you should replace `-DCMAKE_BUILD_TYPE=Debug` with `-DCMAKE_BUILD_TYPE=Release`.

## Compiling
Now, to build the mod, go to the build directory (if you did the previous step you already are)
`cd C:\Users\PinkySmile\SokuProjects\SaveRep\build` and invoke the compiler by running `cmake --build . --target SaveRep`. If you change the name of the mod (in the add_library statement in CMakeLists.txt), you will need to replace 'SaveRep' by the name of your mod in the previous command.

You should find the resulting SaveRep.dll mod inside the build folder that can be to SWRSToys.ini.
In my case, I would add this line to it `SaveRep=C:/Users/PinkySmile/SokuProjects/SaveRep/build/SaveRep.dll`.
1 change: 1 addition & 0 deletions SokuLib
Submodule SokuLib added at a2372e
Binary file added lib/d3d9.lib
Binary file not shown.
Binary file added lib/d3dx9.lib
Binary file not shown.
151 changes: 151 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//
// Created by PinkySmile on 31/10/2020
//

// #include <SokuLib.hpp>
// clang-format off

#include <string>
#include <type_traits>
// clang-format on
#include "BattleManager.hpp"
#include "BattleMode.hpp"
#include "InputManager.hpp"
#include <iostream>
// #include "Net"
#include "Hash.hpp"
#include "NetObject.hpp"
#include "Scenes.hpp"
#include "Tamper.hpp"
#include "VTables.hpp"

static int /*SokuLib::Scene*/ (SokuLib::BattleWatch::*ogBattleWatchOnProcess)();
static int /*SokuLib::Scene*/ (SokuLib::BattleClient::*ogBattleClientOnProcess)();
static int /*SokuLib::Scene*/ (SokuLib::BattleServer::*ogBattleServerOnProcess)();
static const auto spectatingSaveReplayIfAllow = (void(__thiscall *)(SokuLib::NetObject *))(0x454240);
static const auto battleSaveReplay = (void (*)())(0x43ebe0);
static const auto get00899840 = (char *(*)())(0x0043df40);
// static const auto getReplayPath
// = (void(__thiscall *)(SokuLib::InputManager *, char *replay_path, const char *profile1name, const char *profile2name))(0x42cb30);
// static const auto writeReplay = (void(__thiscall *)(SokuLib::InputManager *, const char *path))(0x42b2d0);

static int /*SokuLib::Scene*/ __fastcall CBattleWatch_OnProcess(SokuLib::BattleWatch *This) {
int ret = (This->*ogBattleWatchOnProcess)();
if (ret == SokuLib::SCENE_TITLE) {
std::cout << "Disconnect when spectating. Save replay if allowed." << std::endl;
spectatingSaveReplayIfAllow(&SokuLib::getNetObject());
}
return ret;
}

static void battleSaveReplayIfAllow() {
std::cout << "Save replay if allowed." << std::endl;
switch (get00899840()[0x73]) {
case 0: // always save replay
case 1: // save replay when as player
battleSaveReplay();
case 2: // save replay when as spectator
case 3: // never save replay
case 4: // always ask
break;
}
}

template<typename T, int (T::**ogBattlePlayOnProcess)()> static int /*SokuLib::Scene*/ __fastcall CBattlePlay_OnProcess(T *This) {
int ret = (This->**ogBattlePlayOnProcess)();
if (ret == SokuLib::SCENE_TITLE) {
std::cout << "Disconnect. ";
battleSaveReplayIfAllow();
}
return ret;
}

template<SokuLib::Scene retcode> static void __declspec(naked) gameEndTooEarly() {
static const SokuLib::Scene retcode_ = retcode; // workaround for the template parameter is unusable in inline asm (why?)
std::cout << "Esc or desync causes the game ends too early. ";
battleSaveReplayIfAllow();
__asm {
pop edi;
mov eax, retcode_;
pop esi;
ret;
}
}

static void __declspec(naked) gameEndTooEarly2() {
static auto gameEndTooEarlyAddr = gameEndTooEarly<SokuLib::SCENE_SELECTSV>;
static const void *fun004282d0 = (void *)0x004282d0;
__asm {
call fun004282d0;
jmp gameEndTooEarlyAddr;
}
}
static void __declspec(naked) gameEndTooEarly3() {
static const void *addr004283a2 = (void *)0x004283a2;
__asm {
push esi;
}
std::cout << "Esc or desync causes the game ends too early. ";
battleSaveReplayIfAllow();
__asm {
pop esi;
cmp [esi+0x6c8], 0;
jmp addr004283a2;
}
}

// We check if the game version is what we target (in our case, Soku 1.10a).
extern "C" __declspec(dllexport) bool CheckVersion(const BYTE hash[16]) {
return memcmp(hash, SokuLib::targetHash, sizeof(SokuLib::targetHash)) == 0;
}

// Called when the mod loader is ready to initialize this module.
// All hooks should be placed here. It's also a good moment to load settings
// from the ini.
extern "C" __declspec(dllexport) bool Initialize(HMODULE hMyModule, HMODULE hParentModule) {
DWORD old;

#ifdef _DEBUG
FILE *_;

AllocConsole();
freopen_s(&_, "CONOUT$", "w", stdout);
freopen_s(&_, "CONOUT$", "w", stderr);
#endif
VirtualProtect((PVOID)RDATA_SECTION_OFFSET, RDATA_SECTION_SIZE, PAGE_EXECUTE_WRITECOPY, &old);
ogBattleWatchOnProcess = SokuLib::TamperDword(&SokuLib::VTable_BattleWatch.onProcess, CBattleWatch_OnProcess);
ogBattleServerOnProcess
= SokuLib::TamperDword(&SokuLib::VTable_BattleServer.onProcess, CBattlePlay_OnProcess<SokuLib::BattleServer, &ogBattleServerOnProcess>);
ogBattleClientOnProcess
= SokuLib::TamperDword(&SokuLib::VTable_BattleClient.onProcess, CBattlePlay_OnProcess<SokuLib::BattleClient, &ogBattleClientOnProcess>);
VirtualProtect((PVOID)RDATA_SECTION_OFFSET, RDATA_SECTION_SIZE, old, &old);
VirtualProtect((PVOID)TEXT_SECTION_OFFSET, TEXT_SECTION_SIZE, PAGE_EXECUTE_WRITECOPY, &old);
SokuLib::TamperNearJmp(0x428663, gameEndTooEarly<SokuLib::SCENE_SELECTCL>);
SokuLib::TamperNearJmp(0x428680, gameEndTooEarly<SokuLib::SCENE_SELECTCL>);
SokuLib::TamperNearJmp(0x4283b0, gameEndTooEarly<SokuLib::SCENE_SELECTSV>);
SokuLib::TamperNearJmp(0x42838e, gameEndTooEarly2);
VirtualProtect((PVOID)TEXT_SECTION_OFFSET, TEXT_SECTION_SIZE, old, &old);

FlushInstructionCache(GetCurrentProcess(), nullptr, 0);
return true;
}

extern "C" int APIENTRY DllMain(HMODULE hModule, DWORD fdwReason, LPVOID lpReserved) {
return TRUE;
}

// New mod loader functions
// Loading priority. Mods are loaded in order by ascending level of priority
// (the highest first). When 2 mods define the same loading priority the loading
// order is undefined.
extern "C" __declspec(dllexport) int getPriority() {
return 0;
}

// Not yet implemented in the mod loader, subject to change
// SokuModLoader::IValue **getConfig();
// void freeConfig(SokuModLoader::IValue **v);
// bool commitConfig(SokuModLoader::IValue *);
// const char *getFailureReason();
// bool hasChainedHooks();
// void unHook();
62 changes: 62 additions & 0 deletions src/version.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include <windows.h>
#define VER_FILEVERSION 0,1,0,0
#define VER_FILEVERSION_STR "0.1\0"

#define VER_PRODUCTVERSION 0,1,0,0
#define VER_PRODUCTVERSION_STR "0.1\0"

#define VER_COMPANYNAME_STR "SokuDev\0"
#define VER_FILEDESCRIPTION_STR "Mod for Touhou 12.3 to save replay when the battle ends too early (such as esc or desync)\0"
#define VER_INTERNALNAME_STR "SaveRep\0"
#define VER_LEGALCOPYRIGHT_STR "Hagb\0"
#define VER_LEGALTRADEMARKS1_STR "\0"
#define VER_LEGALTRADEMARKS2_STR "\0"
#define VER_ORIGINALFILENAME_STR "SaveRep.dll\0"
#define VER_PRODUCTNAME_STR "SaveRep\0"

// Define this to 0 if not a pre release
#define VER_PRERELEASE VS_FF_PRERELEASE

#ifndef DEBUG
#define VER_DEBUG 0
#else
#define VER_DEBUG VS_FF_DEBUG
#endif

VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
PRODUCTVERSION VER_PRODUCTVERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS (VER_PRERELEASE|VER_DEBUG)
FILEOS VOS__WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", VER_COMPANYNAME_STR
VALUE "FileDescription", VER_FILEDESCRIPTION_STR
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "InternalName", VER_INTERNALNAME_STR
VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR
VALUE "LegalTrademarks1", VER_LEGALTRADEMARKS1_STR
VALUE "LegalTrademarks2", VER_LEGALTRADEMARKS2_STR
VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR
VALUE "ProductName", VER_PRODUCTNAME_STR
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
END
END

BLOCK "VarFileInfo"
BEGIN
/* The following line should only be modified for localized versions. */
/* It consists of any number of WORD,WORD pairs, with each pair */
/* describing a language,codepage combination supported by the file. */
/* */
/* For example, a file might have values "0x409,1252" indicating that it */
/* supports English language (0x409) in the Windows ANSI codepage (1252). */
VALUE "Translation", 0x409, 1252
END
END

0 comments on commit 7f5b70e

Please sign in to comment.