├── .editorconfig ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── 01-crash-report.yml │ ├── 02-bug-report.yml │ ├── 03-feature-request.yml │ └── config.yml ├── .gitignore ├── .gitmodules ├── BuildScript.ini ├── README.md ├── SA2ModLoader.sln ├── SA2ModLoader ├── CrashDump.cpp ├── CrashDump.h ├── DDS.h ├── DLLData.cpp ├── DLLData.h ├── DebugText.cpp ├── DebugText.h ├── EXEData.cpp ├── EXEData.h ├── Events.cpp ├── Events.h ├── FileMap.cpp ├── FileMap.hpp ├── FileReplacement.cpp ├── FileReplacement.h ├── HelperFunctions.cpp ├── InterpolationFixes.cpp ├── InterpolationFixes.h ├── MediaFns.cpp ├── MediaFns.hpp ├── SA2ModLoader.vcxproj ├── SA2ModLoader.vcxproj.filters ├── TextureReplacement.cpp ├── TextureReplacement.h ├── config.cpp ├── config.h ├── cpp.hint ├── direct3d.cpp ├── direct3d.h ├── dllmain.cpp ├── include │ ├── FunctionHook.h │ ├── MemAccess.h │ ├── SA2Enums.h │ ├── SA2Functions.h │ ├── SA2ModInfo.h │ ├── SA2ModLoader.h │ ├── SA2Structs.h │ ├── SA2Variables.h │ ├── UsercallFunctionHandler.h │ ├── magic.h │ ├── ninja.h │ └── njdef.h ├── json.hpp ├── patches.cpp ├── patches.h ├── pvmx.cpp ├── pvmx.h ├── stdafx.cpp ├── stdafx.h ├── targetver.h ├── testspawn.cpp ├── testspawn.h ├── window.cpp └── window.h ├── UpgradeTool ├── App.config ├── MainForm.Designer.cs ├── MainForm.cs ├── MainForm.resx ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── UpgradeTool.csproj └── WPFDownloadDialog.cs ├── appveyor.yml ├── data ├── Border_Default.png ├── Codes.lst ├── DebugFontTexture.dds ├── DebugTextShader.hlsl └── Patches.json ├── extlib ├── SA2ModLoader.dll └── bass │ ├── COPYING_BASS_VGMSTREAM │ ├── COPYING_VGMSTREAM │ ├── avcodec-vgmstream-59.dll │ ├── avformat-vgmstream-59.dll │ ├── avutil-vgmstream-57.dll │ ├── bass.dll │ ├── bass.h │ ├── bass.lib │ ├── bass_vgmstream.dll │ ├── bass_vgmstream.h │ ├── bass_vgmstream.lib │ ├── libmpg123-0.dll │ ├── libvorbis.dll │ └── swresample-vgmstream-4.dll └── libmodutils ├── AnimationFile.cpp ├── AnimationFile.h ├── GameObject.cpp ├── GameObject.h ├── LandTableInfo.cpp ├── LandTableInfo.h ├── ModelInfo.cpp ├── ModelInfo.h ├── PAKFile.cpp ├── PAKFile.h ├── Trampoline.cpp ├── Trampoline.h ├── libmodutils.vcxproj ├── libmodutils.vcxproj.filters ├── stdafx.cpp └── stdafx.h /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | [*.{cs,c,h,cpp,hpp}] 3 | indent_style=tab -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | git_version.sh text eol=lf 2 | 3 | # Auto detect text files and perform LF normalization 4 | * text=auto 5 | 6 | # Custom for Visual Studio 7 | *.cs diff=csharp 8 | 9 | # Standard to msysgit 10 | *.doc diff=astextplain 11 | *.DOC diff=astextplain 12 | *.docx diff=astextplain 13 | *.DOCX diff=astextplain 14 | *.dot diff=astextplain 15 | *.DOT diff=astextplain 16 | *.pdf diff=astextplain 17 | *.PDF diff=astextplain 18 | *.rtf diff=astextplain 19 | *.RTF diff=astextplain 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01-crash-report.yml: -------------------------------------------------------------------------------- 1 | name: Crash Report 2 | description: Report crashes with the SA2 Mod Loader. 3 | title: "[Crash Report]: " 4 | labels: ["bug", "game crash"] 5 | body: 6 | - type: checkboxes 7 | id: checks 8 | attributes: 9 | label: Required Checks 10 | options: 11 | - label: I have ensured my Mod Loader is updated to the latest version before reporting this crash. 12 | required: true 13 | - label: I have ensured that I meet the [system requirements](https://github.com/X-Hax/sa2-mod-loader#system-requirements) before submitting a report. 14 | required: true 15 | - label: I have followed the troubleshooting guides from the [SA Mod Manager wiki](https://github.com/X-Hax/SA-Mod-Manager/wiki/Troubleshooting-Guide) and they did not resolve my issue. 16 | required: true 17 | - type: textarea 18 | id: report 19 | attributes: 20 | label: Please provide information on the crash you have experienced below. 21 | description: Please include any and all steps taken before and during gameplay regarding the crash. 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: info 26 | attributes: 27 | label: Crash Info 28 | description: Please provide your crash dump information below. You can drag and drop files to the text box to be uploaded. If you do not have crash dump information, please enable the feature in the SA Mod Manager and reproduce the crash. If no crash dump is created with them enabled, please include a list of mods and codes you have active in the texbox instead. 29 | validations: 30 | required: true 31 | - type: dropdown 32 | id: os 33 | attributes: 34 | label: Please select your operating system 35 | options: 36 | - Windows 37 | - Linux 38 | - MacOS 39 | validations: 40 | required: true 41 | - type: input 42 | id: osinfo 43 | attributes: 44 | label: OS Information 45 | description: Please provide the version of your OS. If you're using Linux or MacOS, please provide what Windows emulation layer it is that you're using. 46 | placeholder: e.g. Ubuntu 24.04.2, Wine 10.0 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02-bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug with the SA2 Mod Loader. 3 | title: "[Bug Report]: " 4 | labels: ["bug"] 5 | body: 6 | - type: checkboxes 7 | id: checks 8 | attributes: 9 | label: Required Checks 10 | options: 11 | - label: I have checked through existing issues and my bug has not been reported. 12 | required: true 13 | - label: I have ensured my Mod Loader is updated to the latest version before reporting this bug. 14 | required: true 15 | - label: I have ensured that I meet the [system requirements](https://github.com/X-Hax/sa2-mod-loader#system-requirements) before submitting a report. 16 | required: true 17 | - label: I have followed the troubleshooting guides from the [SA Mod Manager wiki](https://github.com/X-Hax/SA-Mod-Manager/wiki/Troubleshooting-Guide) and they did not resolve my issue. 18 | required: true 19 | - type: textarea 20 | id: report 21 | attributes: 22 | label: Please provide information on the bug you're experiencing below. 23 | description: Please include any and all steps taken to reach the bug you are experiencing. For mod developers, if confirmed to be an issue with the mod loader, please include the steps you used for creating your mod. 24 | validations: 25 | required: true 26 | - type: dropdown 27 | id: os 28 | attributes: 29 | label: Please select your operating system 30 | options: 31 | - Windows 32 | - Linux 33 | - MacOS 34 | validations: 35 | required: true 36 | - type: input 37 | id: osinfo 38 | attributes: 39 | label: OS Information 40 | description: Please provide the version of your OS. If you're using Linux or MacOS, please provide what translation layer you're using. 41 | placeholder: e.g. Ubuntu 24.04.2, Wine 10.0 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/03-feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Request a feature for the SA2 Mod Loader 3 | title: "[Feature Request]: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Feature Requests include new API capabilities, new codes, or new patches. 10 | - type: textarea 11 | id: request 12 | attributes: 13 | label: Please provide information regarding your request 14 | description: Please include what the purpose is of this request and why you believe it should be part of the Mod Loader. If this is a new API feature, please include how you believe it should function as well. 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: info 19 | attributes: 20 | label: Additional Information 21 | description: If there is anything additional would you like to provide, such as screenshots or files, please include them here. 22 | validations: 23 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: SA Mod Manager 4 | url: https://github.com/X-Hax/SA-Mod-Manager 5 | about: Issues with the Mod Manager for SADX and SA2 should be reported here. 6 | - name: SADX Mod Loader 7 | url: https://github.com/X-Hax/sadx-mod-loader 8 | about: Issues with the SADX Mod Loader should be reported here. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Visual Studio 3 | ################# 4 | 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.sln.docstates 12 | .vs/ 13 | 14 | # Build results 15 | 16 | [Dd]ebug/ 17 | [Rr]elease/ 18 | x64/ 19 | build/ 20 | [Bb]in/ 21 | [Oo]bj/ 22 | 23 | # MSTest test Results 24 | [Tt]est[Rr]esult*/ 25 | [Bb]uild[Ll]og.* 26 | 27 | *_i.c 28 | *_p.c 29 | *.ilk 30 | *.meta 31 | *.obj 32 | *.pch 33 | *.pdb 34 | *.pgc 35 | *.pgd 36 | *.rsp 37 | *.sbr 38 | *.tlb 39 | *.tli 40 | *.tlh 41 | *.tmp 42 | *.tmp_proj 43 | *.log 44 | *.vspscc 45 | *.vssscc 46 | .builds 47 | *.pidb 48 | *.log 49 | *.scc 50 | 51 | # Visual C++ cache files 52 | ipch/ 53 | *.aps 54 | *.ncb 55 | *.opensdf 56 | *.sdf 57 | *.cachefile 58 | 59 | # Visual Studio profiler 60 | *.psess 61 | *.vsp 62 | *.vspx 63 | *.opendb 64 | *.db 65 | 66 | /packages 67 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "mod-loader-common"] 2 | path = mod-loader-common 3 | url = https://github.com/sonicretro/mod-loader-common.git 4 | -------------------------------------------------------------------------------- /BuildScript.ini: -------------------------------------------------------------------------------- 1 | programming=SA2ModLoader\\include 2 | programming\\AnimationFile.cpp=libmodutils\\AnimationFile.cpp 3 | programming\\AnimationFile.h=libmodutils\\AnimationFile.h 4 | programming\\GameObject.cpp=libmodutils\\GameObject.cpp 5 | programming\\GameObject.h=libmodutils\\GameObject.h 6 | programming\\LandTableInfo.cpp=libmodutils\\LandTableInfo.cpp 7 | programming\\LandTableInfo.h=libmodutils\\LandTableInfo.h 8 | programming\\ModelInfo.cpp=libmodutils\\ModelInfo.cpp 9 | programming\\ModelInfo.h=libmodutils\\ModelInfo.h 10 | programming\\PAKFile.cpp=libmodutils\\PAKFile.cpp 11 | programming\\PAKFile.h=libmodutils\\PAKFile.h 12 | programming\\Trampoline.cpp=libmodutils\\Trampoline.cpp 13 | programming\\Trampoline.h=libmodutils\\Trampoline.h 14 | programming\\IniFile.cpp=mod-loader-common\\ModLoaderCommon\\IniFile.cpp 15 | programming\\IniFile.hpp=mod-loader-common\\ModLoaderCommon\\IniFile.hpp 16 | programming\\TextConv.cpp=mod-loader-common\\ModLoaderCommon\\TextConv.cpp 17 | programming\\TextConv.hpp=mod-loader-common\\ModLoaderCommon\\TextConv.hpp -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sonic Adventure 2 PC Mod Loader 2 | 3 | ## System Requirements 4 | 5 | To use SA2 Mod Loader, you must have: 6 | * Sonic Adventure 2 PC, 2012 Steam version 7 | * Windows 7 or later 8 | * [Visual C++ 2022 runtime (x86)](https://aka.ms/vs/17/release/vc_redist.x86.exe) 9 | 10 | To manage mods and edit settings, you also need the following: 11 | * [SA Mod Manager](https://github.com/X-Hax/SA-Mod-Manager) 12 | * .NET 8.0 Desktop runtime 13 | 14 | ## License 15 | 16 | DISCLAIMER: 17 | Any and all content presented in this repository is presented for 18 | informational and educational purposes only. Commercial usage is 19 | expressly prohibited. X-Hax claims no ownership of any code 20 | in these repositories. You assume any and all responsibility for 21 | using this content responsibly. X-Hax claims no responsibility 22 | or warranty. 23 | 24 | 25 | ## Trademarks 26 | 27 | Sega, Sonic, Sonic the Hedgehog, and Sonic Adventure 2 are either 28 | trademarks or registered trademarks of SEGA of America, Inc. 29 | -------------------------------------------------------------------------------- /SA2ModLoader.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.34931.43 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SA2ModLoader", "SA2ModLoader\SA2ModLoader.vcxproj", "{3BF5C1B2-E3A4-43E7-9C2A-E445810834DE}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {83C0F6B3-2297-4A14-98D6-F9C3E99192CE} = {83C0F6B3-2297-4A14-98D6-F9C3E99192CE} 9 | {EC0293F5-4BCF-46B2-8133-18CAEA141C5B} = {EC0293F5-4BCF-46B2-8133-18CAEA141C5B} 10 | EndProjectSection 11 | EndProject 12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmodutils", "libmodutils\libmodutils.vcxproj", "{83C0F6B3-2297-4A14-98D6-F9C3E99192CE}" 13 | EndProject 14 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ModLoaderCommon", "mod-loader-common\ModLoaderCommon\ModLoaderCommon.vcxproj", "{EC0293F5-4BCF-46B2-8133-18CAEA141C5B}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{16CD3F5E-52B1-4EAD-AF19-2C533CE508EA}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|x86 = Debug|x86 21 | Release|x86 = Release|x86 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {3BF5C1B2-E3A4-43E7-9C2A-E445810834DE}.Debug|x86.ActiveCfg = Debug|Win32 25 | {3BF5C1B2-E3A4-43E7-9C2A-E445810834DE}.Debug|x86.Build.0 = Debug|Win32 26 | {3BF5C1B2-E3A4-43E7-9C2A-E445810834DE}.Release|x86.ActiveCfg = Release|Win32 27 | {3BF5C1B2-E3A4-43E7-9C2A-E445810834DE}.Release|x86.Build.0 = Release|Win32 28 | {83C0F6B3-2297-4A14-98D6-F9C3E99192CE}.Debug|x86.ActiveCfg = Debug|Win32 29 | {83C0F6B3-2297-4A14-98D6-F9C3E99192CE}.Debug|x86.Build.0 = Debug|Win32 30 | {83C0F6B3-2297-4A14-98D6-F9C3E99192CE}.Release|x86.ActiveCfg = Release|Win32 31 | {83C0F6B3-2297-4A14-98D6-F9C3E99192CE}.Release|x86.Build.0 = Release|Win32 32 | {EC0293F5-4BCF-46B2-8133-18CAEA141C5B}.Debug|x86.ActiveCfg = Debug|Win32 33 | {EC0293F5-4BCF-46B2-8133-18CAEA141C5B}.Debug|x86.Build.0 = Debug|Win32 34 | {EC0293F5-4BCF-46B2-8133-18CAEA141C5B}.Release|x86.ActiveCfg = Release|Win32 35 | {EC0293F5-4BCF-46B2-8133-18CAEA141C5B}.Release|x86.Build.0 = Release|Win32 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(NestedProjects) = preSolution 41 | {EC0293F5-4BCF-46B2-8133-18CAEA141C5B} = {16CD3F5E-52B1-4EAD-AF19-2C533CE508EA} 42 | EndGlobalSection 43 | GlobalSection(ExtensibilityGlobals) = postSolution 44 | SolutionGuid = {0260A4DD-F2E9-4215-8FAB-0E22D1EB58AC} 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /SA2ModLoader/CrashDump.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void initCrashDump(); -------------------------------------------------------------------------------- /SA2ModLoader/DDS.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | //-------------------------------------------------------------------------------------- 3 | // DDS file structure definitions 4 | // 5 | // See DDS.h in the 'Texconv' sample and the 'DirectXTex' library 6 | //-------------------------------------------------------------------------------------- 7 | 8 | #include 9 | 10 | #pragma pack(push,1) 11 | 12 | const uint32_t DDS_MAGIC = 0x20534444; // "DDS " 13 | 14 | struct DDS_PIXELFORMAT 15 | { 16 | uint32_t size; 17 | uint32_t flags; 18 | uint32_t fourCC; 19 | uint32_t RGBBitCount; 20 | uint32_t RBitMask; 21 | uint32_t GBitMask; 22 | uint32_t BBitMask; 23 | uint32_t ABitMask; 24 | }; 25 | 26 | #define DDS_FOURCC 0x00000004 // DDPF_FOURCC 27 | #define DDS_RGB 0x00000040 // DDPF_RGB 28 | #define DDS_LUMINANCE 0x00020000 // DDPF_LUMINANCE 29 | #define DDS_ALPHA 0x00000002 // DDPF_ALPHA 30 | 31 | #define DDS_HEADER_FLAGS_VOLUME 0x00800000 // DDSD_DEPTH 32 | 33 | #define DDS_HEIGHT 0x00000002 // DDSD_HEIGHT 34 | #define DDS_WIDTH 0x00000004 // DDSD_WIDTH 35 | 36 | #define DDS_CUBEMAP_POSITIVEX 0x00000600 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX 37 | #define DDS_CUBEMAP_NEGATIVEX 0x00000a00 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX 38 | #define DDS_CUBEMAP_POSITIVEY 0x00001200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY 39 | #define DDS_CUBEMAP_NEGATIVEY 0x00002200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY 40 | #define DDS_CUBEMAP_POSITIVEZ 0x00004200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ 41 | #define DDS_CUBEMAP_NEGATIVEZ 0x00008200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ 42 | 43 | #define DDS_CUBEMAP_ALLFACES ( DDS_CUBEMAP_POSITIVEX | DDS_CUBEMAP_NEGATIVEX |\ 44 | DDS_CUBEMAP_POSITIVEY | DDS_CUBEMAP_NEGATIVEY |\ 45 | DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEZ ) 46 | 47 | #define DDS_CUBEMAP 0x00000200 // DDSCAPS2_CUBEMAP 48 | 49 | enum DDS_MISC_FLAGS2 50 | { 51 | DDS_MISC_FLAGS2_ALPHA_MODE_MASK = 0x7L, 52 | }; 53 | 54 | struct DDS_HEADER 55 | { 56 | uint32_t size; 57 | uint32_t flags; 58 | uint32_t height; 59 | uint32_t width; 60 | uint32_t pitchOrLinearSize; 61 | uint32_t depth; // only if DDS_HEADER_FLAGS_VOLUME is set in flags 62 | uint32_t mipMapCount; 63 | uint32_t reserved1[11]; 64 | DDS_PIXELFORMAT ddspf; 65 | uint32_t caps; 66 | uint32_t caps2; 67 | uint32_t caps3; 68 | uint32_t caps4; 69 | uint32_t reserved2; 70 | }; 71 | 72 | /* 73 | struct DDS_HEADER_DXT10 74 | { 75 | DXGI_FORMAT dxgiFormat; 76 | uint32_t resourceDimension; 77 | uint32_t miscFlag; // see D3D11_RESOURCE_MISC_FLAG 78 | uint32_t arraySize; 79 | uint32_t miscFlags2; 80 | }; 81 | */ 82 | 83 | #pragma pack(pop) 84 | 85 | -------------------------------------------------------------------------------- /SA2ModLoader/DLLData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | void ProcessDLLData(const wchar_t* filename, const std::wstring& mod_dir); 5 | -------------------------------------------------------------------------------- /SA2ModLoader/DebugText.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace debug_text 4 | { 5 | void Initialize(); 6 | void DisplayGameDebug(const char* buf); 7 | void SetFontSize(float size); 8 | void SetFontColor(int color); 9 | void DisplayString(int loc, const char* str); 10 | void DisplayNumber(int loc, int value, int numdigits); 11 | void DisplayStringFormatted(int loc, const char* Format, ...); 12 | void sub_759AA0(int a1, int a2, int a3, int a4, int a5); 13 | } -------------------------------------------------------------------------------- /SA2ModLoader/EXEData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | void ProcessEXEData(const wchar_t *filename, const std::wstring &mod_dir); 5 | -------------------------------------------------------------------------------- /SA2ModLoader/Events.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CodeParser.hpp" 3 | 4 | std::vector modFrameEvents; 5 | std::vector modInputEvents; 6 | std::vector modControlEvents; 7 | std::vector modExitEvents; 8 | std::vector modCustomTextureLoadEvents; 9 | std::vector modRenderDeviceLost; 10 | std::vector modRenderDeviceReset; 11 | std::vector onRenderSceneEnd; 12 | std::vector onRenderSceneStart; 13 | CodeParser codeParser; 14 | 15 | Trampoline exitDetour(0x7ACA2E, 0x7ACA35, OnExit); 16 | /** 17 | * Registers an event to the specified event list. 18 | * @param eventList The event list to add to. 19 | * @param module The module for the mod DLL. 20 | * @param name The name of the exported function from the module (i.e OnFrame) 21 | */ 22 | void RegisterEvent(std::vector& eventList, HMODULE module, const char* name) 23 | { 24 | const ModEvent modEvent = (const ModEvent)GetProcAddress(module, name); 25 | 26 | if (modEvent != nullptr) 27 | eventList.push_back(modEvent); 28 | } 29 | 30 | void __cdecl OnFrame() 31 | { 32 | codeParser.processCodeList(); 33 | RaiseEvents(modFrameEvents); 34 | } 35 | 36 | #pragma region OnFrame Initialization 37 | 38 | void* caseDefault_ptr = (void*)0x004340CC; 39 | void* case08_ptr = (void*)0x0043405D; 40 | void* case09_ptr = (void*)0x0043407E; 41 | void* case10_ptr = (void*)0x004340B6; 42 | 43 | void __declspec(naked) OnFrame_MidJump() 44 | { 45 | __asm 46 | { 47 | push eax 48 | call OnFrame 49 | pop eax 50 | 51 | pop edi 52 | pop esi 53 | pop ebp 54 | pop ebx 55 | pop ecx 56 | retn 57 | } 58 | } 59 | 60 | void* OnFrame_Hook_ptr = (void*)0x004340E7; 61 | void __declspec(naked) OnFrame_Hook() 62 | { 63 | __asm 64 | { 65 | push eax 66 | call OnFrame 67 | pop eax 68 | retn 69 | } 70 | } 71 | 72 | void InitOnFrame() 73 | { 74 | WriteJump(case08_ptr, OnFrame_MidJump); 75 | WriteJump(case09_ptr, OnFrame_MidJump); 76 | WriteJump(case10_ptr, OnFrame_MidJump); 77 | 78 | // OnFrame caseDefault 79 | // Occurs if the current game mode isn't 8, 9 or 10, and byte_174AFF9 == 1 80 | WriteJump(caseDefault_ptr, OnFrame_MidJump); 81 | 82 | // OnFrame OnFrame_Hook 83 | // Occurs at the end of the function (effectively the "else" to the statement above) 84 | WriteJump(OnFrame_Hook_ptr, OnFrame_Hook); 85 | } 86 | 87 | #pragma endregion 88 | 89 | void __cdecl OnInput() 90 | { 91 | RaiseEvents(modInputEvents); 92 | } 93 | 94 | void __cdecl OnControl() 95 | { 96 | RaiseEvents(modControlEvents); 97 | } 98 | 99 | void __cdecl OnExit(UINT uExitCode, int a1, int a2) 100 | { 101 | RaiseEvents(modExitEvents); 102 | NonStaticFunctionPointer(void, original, (UINT, int, int), exitDetour.Target()); 103 | original(uExitCode, a1, a2); 104 | } 105 | -------------------------------------------------------------------------------- /SA2ModLoader/Events.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SA2ModLoader.h" 4 | #include "CodeParser.hpp" 5 | #include 6 | 7 | using TextureLoadEvent = void(__cdecl*)(NJS_TEXMANAGE*, const char*, Uint32); 8 | 9 | extern std::vector modFrameEvents; 10 | extern std::vector modInputEvents; 11 | extern std::vector modControlEvents; 12 | extern std::vector modExitEvents; 13 | extern std::vector modCustomTextureLoadEvents; 14 | extern std::vector modRenderDeviceLost; 15 | extern std::vector modRenderDeviceReset; 16 | extern std::vector onRenderSceneEnd; 17 | extern std::vector onRenderSceneStart; 18 | extern CodeParser codeParser; 19 | 20 | void RegisterEvent(std::vector& eventList, HMODULE module, const char* name); 21 | 22 | /** 23 | * Calls all registered events in the specified event list. 24 | * @param eventList The list of events to trigger. 25 | */ 26 | static inline void RaiseEvents(const std::vector& eventList) 27 | { 28 | for (auto& i : eventList) 29 | i(); 30 | } 31 | 32 | void InitOnFrame(); 33 | void __cdecl OnFrame(); 34 | void __cdecl OnInput(); 35 | void __cdecl OnControl(); 36 | void __cdecl OnExit(UINT uExitCode, int a1, int a2); -------------------------------------------------------------------------------- /SA2ModLoader/FileMap.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * SADX Mod Loader 3 | * File remapper. 4 | */ 5 | 6 | #ifndef FILEMAP_HPP 7 | #define FILEMAP_HPP 8 | 9 | #include 10 | #include 11 | 12 | class FileMap 13 | { 14 | public: 15 | FileMap() = default; 16 | ~FileMap() = default; 17 | 18 | // Disable the copy and assign constructors. 19 | FileMap(const FileMap&) = delete; 20 | FileMap& operator=(const FileMap&) = delete; 21 | 22 | /** 23 | * Normalize a filename for the file replacement map. 24 | * @param filename Filename. 25 | * @return Normalized filename. 26 | */ 27 | static std::string normalizePath(const std::string& filename); 28 | 29 | /** 30 | * Normalize a filename for the file replacement map. 31 | * @param filename Filename. 32 | * @return Normalized filename. 33 | */ 34 | static std::string normalizePath(const char* filename); 35 | 36 | /** 37 | * Ignore a file. 38 | * @param ignoreFile File to ignore. 39 | * @param modIdx Index of the current mod. 40 | */ 41 | void addIgnoreFile(const std::string& ignoreFile, int modIdx); 42 | 43 | /** 44 | * Add a file replacement. 45 | * @param origFile Original filename. 46 | * @param modFile Mod filename. 47 | */ 48 | void addReplaceFile(const std::string& origFile, const std::string& modFile); 49 | 50 | /** 51 | * Remove a file replacement. 52 | * @param file Filename. 53 | */ 54 | void FileMap::unreplaceFile(const std::string& file); 55 | 56 | /** 57 | * Swap two files. 58 | * @param fileA First filename. 59 | * @param fileB Second filename. 60 | */ 61 | void swapFiles(const std::string& fileA, const std::string& fileB); 62 | 63 | /** 64 | * Recursively scan a directory and add all files to the replacement map. 65 | * Destination is always relative to system/. 66 | * @param srcPath Path to scan. 67 | * @param modIdx Index of the current mod. 68 | */ 69 | void scanFolder(const std::string& srcPath, int modIdx); 70 | 71 | void scanPRSFolder(const std::string& srcPath); 72 | 73 | /** 74 | * Scans a texture pack folder for 75 | * @param srcPath The path to the "textures" folder to scan. 76 | * @param modIndex Index of the current mod. 77 | */ 78 | void scanTextureFolder(const std::string& srcPath, int modIndex); 79 | 80 | protected: 81 | /** 82 | * Recursively scan a directory and add all files to the replacement map. 83 | * Destination is always relative to system/. 84 | * (Internal recursive function) 85 | * @param srcPath Path to scan. 86 | * @param srcLen Length of original srcPath. (used for recursion) 87 | * @param modIdx Index of the current mod. 88 | */ 89 | void scanFolder_int(const std::string& srcPath, int srcLen, int modIdx); 90 | 91 | void scanPRSFolder_int(const std::string& srcPath, int srcLen, int modIdx); 92 | 93 | /** 94 | * Set a replacement file in the map. 95 | * Filenames must already be normalized! 96 | * (Internal function; handles memory allocation) 97 | * @param origFile Original file. 98 | * @param destFile Mod filename. 99 | * @param modIdx Index of the current mod. 100 | */ 101 | void setReplaceFile(const std::string& origFile, const std::string& destFile, int modIdx); 102 | 103 | public: 104 | /** 105 | * Get a filename from the file replacement map. 106 | * @param lpFileName Filename. 107 | * @return Replaced filename, or original filename if not replaced by a mod. 108 | */ 109 | const char* replaceFile(const char* lpFileName) const; 110 | 111 | /** 112 | * Get a filename from the file replacement map. 113 | * @param[in] lpFileName Filename. 114 | * @param[out] modIndex Index of the mod that replaced a file, or 0 if no mod replaced it. 115 | * @return Replaced filename, or original filename if not replaced by a mod. 116 | */ 117 | const char* replaceFile(const char* lpFileName, int& modIndex) const; 118 | 119 | /** 120 | * Get the index of the mod that replaced a given file. 121 | * @param lpFileName Filename. 122 | * @return Index of the mod that replaced a file, or 0 if no mod replaced it. 123 | */ 124 | int getModIndex(const char* lpFileName) const; 125 | 126 | /** 127 | * Clear the file replacement map. 128 | */ 129 | void clear(); 130 | 131 | /** 132 | * Set the SA2 directory. 133 | */ 134 | void setSA2Dir(std::string dir); 135 | 136 | protected: 137 | 138 | struct Entry 139 | { 140 | std::string fileName; 141 | int modIndex; 142 | }; 143 | 144 | /** 145 | * File replacement map. 146 | * - Key: Original filename. 147 | * - Value: New filename. 148 | */ 149 | std::unordered_map m_fileMap; 150 | std::string sa2dir; 151 | }; 152 | 153 | #endif /* FILEMAP_HPP */ 154 | -------------------------------------------------------------------------------- /SA2ModLoader/FileReplacement.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | 3 | // File replacement map. 4 | // NOTE: Do NOT mark this as static. 5 | // MediaFns.cpp needs to access the FileMap. 6 | FileMap sadx_fileMap; 7 | 8 | /** 9 | * CreateFileA() wrapper using _ReplaceFile(). 10 | * @param lpFileName 11 | * @param dwDesiredAccess 12 | * @param dwShareMode 13 | * @param lpSecurityAttributes 14 | * @param dwCreationDisposition 15 | * @param dwFlagsAndAttributes 16 | * @param hTemplateFile 17 | * @return 18 | */ 19 | HANDLE WINAPI MyCreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) 20 | { 21 | return CreateFileA(sadx_fileMap.replaceFile(lpFileName), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); 22 | } 23 | 24 | /** 25 | * C wrapper to call sadx_fileMap.replaceFile() from asm. 26 | * @param lpFileName Filename. 27 | * @return Replaced filename, or original filename if not replaced by a mod. 28 | */ 29 | const char *_ReplaceFile(const char *lpFileName) 30 | { 31 | return sadx_fileMap.replaceFile(lpFileName); 32 | } 33 | -------------------------------------------------------------------------------- /SA2ModLoader/FileReplacement.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | #include "FileMap.hpp" 6 | 7 | extern FileMap sadx_fileMap; 8 | 9 | HANDLE __stdcall MyCreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); 10 | const char* _ReplaceFile(const char* lpFileName); 11 | -------------------------------------------------------------------------------- /SA2ModLoader/HelperFunctions.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "SA2ModLoader.h" 3 | #include "DebugText.h" 4 | #include "TextureReplacement.h" 5 | #include "FileSystem.h" 6 | #include "InterpolationFixes.h" 7 | 8 | using namespace std; 9 | 10 | extern unordered_map> StartPositions; 11 | extern bool StartPositionsModified; 12 | 13 | void RegisterStartPosition(unsigned char character, const StartPosition& position) 14 | { 15 | switch (character) 16 | { 17 | case Characters_Sonic: 18 | case Characters_Shadow: 19 | case Characters_Tails: 20 | case Characters_Eggman: 21 | case Characters_Knuckles: 22 | case Characters_Rouge: 23 | case Characters_MechTails: 24 | case Characters_MechEggman: 25 | case Characters_SuperSonic: 26 | case Characters_SuperShadow: 27 | StartPositions[character][position.Level] = position; 28 | StartPositionsModified = true; 29 | break; 30 | } 31 | } 32 | 33 | void ClearStartPositionList(unsigned char character) 34 | { 35 | switch (character) 36 | { 37 | case Characters_Sonic: 38 | case Characters_Shadow: 39 | case Characters_Tails: 40 | case Characters_Eggman: 41 | case Characters_Knuckles: 42 | case Characters_Rouge: 43 | case Characters_MechTails: 44 | case Characters_MechEggman: 45 | case Characters_SuperSonic: 46 | case Characters_SuperShadow: 47 | StartPositions[character].clear(); 48 | StartPositionsModified = true; 49 | break; 50 | } 51 | } 52 | 53 | extern unordered_map> _2PIntroPositions; 54 | extern bool _2PIntroPositionsModified; 55 | 56 | void Register2PIntroPosition(unsigned char character, const LevelEndPosition& position) 57 | { 58 | switch (character) 59 | { 60 | case Characters_Sonic: 61 | case Characters_Shadow: 62 | case Characters_Tails: 63 | case Characters_Eggman: 64 | case Characters_Knuckles: 65 | case Characters_Rouge: 66 | case Characters_MechTails: 67 | case Characters_MechEggman: 68 | case Characters_SuperSonic: 69 | case Characters_SuperShadow: 70 | _2PIntroPositions[character][position.Level] = position; 71 | _2PIntroPositionsModified = true; 72 | break; 73 | } 74 | } 75 | 76 | void Clear2PIntroPositionList(unsigned char character) 77 | { 78 | switch (character) 79 | { 80 | case Characters_Sonic: 81 | case Characters_Shadow: 82 | case Characters_Tails: 83 | case Characters_Eggman: 84 | case Characters_Knuckles: 85 | case Characters_Rouge: 86 | case Characters_MechTails: 87 | case Characters_MechEggman: 88 | case Characters_SuperSonic: 89 | case Characters_SuperShadow: 90 | _2PIntroPositions[character].clear(); 91 | _2PIntroPositionsModified = true; 92 | break; 93 | } 94 | } 95 | 96 | extern unordered_map> EndPositions; 97 | extern bool EndPositionsModified; 98 | 99 | void RegisterEndPosition(unsigned char character, const StartPosition& position) 100 | { 101 | switch (character) 102 | { 103 | case Characters_Sonic: 104 | case Characters_Shadow: 105 | case Characters_Tails: 106 | case Characters_Eggman: 107 | case Characters_Knuckles: 108 | case Characters_Rouge: 109 | case Characters_MechTails: 110 | case Characters_MechEggman: 111 | case Characters_SuperSonic: 112 | case Characters_SuperShadow: 113 | EndPositions[character][position.Level] = position; 114 | EndPositionsModified = true; 115 | break; 116 | } 117 | } 118 | 119 | void ClearEndPositionList(unsigned char character) 120 | { 121 | switch (character) 122 | { 123 | case Characters_Sonic: 124 | case Characters_Shadow: 125 | case Characters_Tails: 126 | case Characters_Eggman: 127 | case Characters_Knuckles: 128 | case Characters_Rouge: 129 | case Characters_MechTails: 130 | case Characters_MechEggman: 131 | case Characters_SuperSonic: 132 | case Characters_SuperShadow: 133 | EndPositions[character].clear(); 134 | EndPositionsModified = true; 135 | break; 136 | } 137 | } 138 | 139 | extern bool Mission23EndPositionsModified; 140 | extern unordered_map> Mission23EndPositions; 141 | 142 | void RegisterMission23EndPosition(unsigned char character, const LevelEndPosition& position) 143 | { 144 | switch (character) 145 | { 146 | case Characters_Sonic: 147 | case Characters_Shadow: 148 | case Characters_Tails: 149 | case Characters_Eggman: 150 | case Characters_Knuckles: 151 | case Characters_Rouge: 152 | case Characters_MechTails: 153 | case Characters_MechEggman: 154 | case Characters_SuperSonic: 155 | case Characters_SuperShadow: 156 | Mission23EndPositions[character][position.Level] = position; 157 | Mission23EndPositionsModified = true; 158 | break; 159 | } 160 | } 161 | 162 | void ClearMission23EndPositionList(unsigned char character) 163 | { 164 | switch (character) 165 | { 166 | case Characters_Sonic: 167 | case Characters_Shadow: 168 | case Characters_Tails: 169 | case Characters_Eggman: 170 | case Characters_Knuckles: 171 | case Characters_Rouge: 172 | case Characters_MechTails: 173 | case Characters_MechEggman: 174 | case Characters_SuperSonic: 175 | case Characters_SuperShadow: 176 | Mission23EndPositions[character].clear(); 177 | Mission23EndPositionsModified = true; 178 | break; 179 | } 180 | } 181 | 182 | extern const char* mainsavepath; 183 | const char* GetMainSavePath() 184 | { 185 | return mainsavepath; 186 | } 187 | 188 | extern const char* chaosavepath; 189 | const char* GetChaoSavePath() 190 | { 191 | return chaosavepath; 192 | } 193 | 194 | void HookExport(LPCSTR exportName, const void* newdata) 195 | { 196 | intptr_t hModule = (intptr_t) * *datadllhandle; 197 | ULONG ulSize = 0; 198 | PIMAGE_EXPORT_DIRECTORY pExportDesc = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryEntryToData( 199 | **datadllhandle, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &ulSize); 200 | 201 | if (pExportDesc != nullptr) 202 | { 203 | intptr_t* funcaddrs = (intptr_t*)(hModule + pExportDesc->AddressOfFunctions); 204 | intptr_t* nameaddrs = (intptr_t*)(hModule + pExportDesc->AddressOfNames); 205 | short* ordaddrs = (short*)(hModule + pExportDesc->AddressOfNameOrdinals); 206 | 207 | for (unsigned int i = 0; i < pExportDesc->NumberOfNames; ++i) 208 | { 209 | LPCSTR ename = (LPCSTR)(hModule + nameaddrs[i]); 210 | 211 | if (!lstrcmpiA(ename, exportName)) 212 | { 213 | auto thing = &funcaddrs[ordaddrs[i]]; 214 | DWORD dwOldProtect = 0; 215 | VirtualProtect(thing, sizeof(intptr_t), PAGE_WRITECOPY, &dwOldProtect); 216 | *thing = (intptr_t)newdata - hModule; 217 | VirtualProtect(thing, sizeof(intptr_t), dwOldProtect, &dwOldProtect); 218 | } 219 | } 220 | } 221 | } 222 | 223 | const char* __cdecl GetReplaceablePath(const char* path) 224 | { 225 | return sadx_fileMap.replaceFile(path); 226 | } 227 | 228 | void _ReplaceFile(const char* src, const char* dst) 229 | { 230 | string orig = sadx_fileMap.normalizePath(src); 231 | string mod = sadx_fileMap.normalizePath(dst); 232 | sadx_fileMap.addReplaceFile(orig, mod); 233 | auto i = orig.find("\\prs\\"); 234 | if (i != string::npos) 235 | { 236 | orig.erase(i, 4); 237 | ReplaceFileExtension(orig, ".prs"); 238 | sadx_fileMap.addReplaceFile(orig, mod); 239 | } 240 | } 241 | 242 | void SetWindowTitle(const wchar_t* title) 243 | { 244 | if (MainWindowHandle) 245 | SetWindowTextW(MainWindowHandle, title); 246 | } 247 | 248 | extern std::vector modlist; 249 | 250 | namespace ModListImpl 251 | { 252 | ModList::iterator begin() 253 | { 254 | return &*modlist.cbegin(); 255 | } 256 | 257 | ModList::iterator end() 258 | { 259 | return &*modlist.cend(); 260 | } 261 | 262 | ModList::reference at(ModList::size_type pos) 263 | { 264 | return modlist[pos]; 265 | } 266 | 267 | ModList::pointer data() 268 | { 269 | return modlist.data(); 270 | } 271 | 272 | ModList::size_type size() 273 | { 274 | return modlist.size(); 275 | } 276 | 277 | ModList::iterator find(const char* id) 278 | { 279 | for (auto& iter : modlist) 280 | if (!strcmp(iter.ID, id)) 281 | return &iter; 282 | return nullptr; 283 | } 284 | 285 | ModList::iterator find_by_name(const char* name) 286 | { 287 | for (auto& iter : modlist) 288 | if (!strcmp(iter.Name, name)) 289 | return &iter; 290 | return nullptr; 291 | } 292 | 293 | ModList::iterator find_by_folder(const char* folder) 294 | { 295 | for (auto& iter : modlist) 296 | if (!strcmp(iter.Folder, folder)) 297 | return &iter; 298 | return nullptr; 299 | } 300 | 301 | ModList::iterator find_by_dll(HMODULE handle) 302 | { 303 | if (handle == nullptr) 304 | return nullptr; 305 | for (auto& iter : modlist) 306 | if (iter.DLLHandle == handle) 307 | return &iter; 308 | return nullptr; 309 | } 310 | } 311 | 312 | ModList modList = { 313 | ModListImpl::begin, 314 | ModListImpl::end, 315 | ModListImpl::at, 316 | ModListImpl::data, 317 | ModListImpl::size, 318 | ModListImpl::find, 319 | ModListImpl::find_by_name, 320 | ModListImpl::find_by_folder, 321 | ModListImpl::find_by_dll 322 | }; 323 | 324 | uint16_t voicenum = 2727; 325 | uint16_t RegisterVoice(const char* fileJP, const char* fileEN) 326 | { 327 | if (voicenum == UINT16_MAX) 328 | return 0; 329 | char buf[MAX_PATH]; 330 | sprintf_s(buf, "resource\\gd_pc\\event_adx\\%04d.ahx", voicenum); 331 | _ReplaceFile(buf, fileJP); 332 | sprintf_s(buf, "resource\\gd_pc\\event_adx_e\\%04d.ahx", voicenum); 333 | _ReplaceFile(buf, fileEN); 334 | return voicenum++; 335 | } 336 | 337 | void UnreplaceFile(const char* file) 338 | { 339 | sadx_fileMap.unreplaceFile(file); 340 | } 341 | 342 | void PushInterpolationFix() 343 | { 344 | interpolation::push(); 345 | } 346 | 347 | void PopInterpolationFix() 348 | { 349 | interpolation::pop(); 350 | } 351 | 352 | extern LoaderSettings loaderSettings; 353 | 354 | HelperFunctions helperFunctions = { 355 | ModLoaderVer, 356 | RegisterStartPosition, 357 | ClearStartPositionList, 358 | Register2PIntroPosition, 359 | Clear2PIntroPositionList, 360 | GetMainSavePath, 361 | GetChaoSavePath, 362 | RegisterEndPosition, 363 | ClearEndPositionList, 364 | RegisterMission23EndPosition, 365 | ClearMission23EndPositionList, 366 | HookExport, 367 | GetReplaceablePath, 368 | _ReplaceFile, 369 | SetWindowTitle, 370 | debug_text::SetFontSize, 371 | debug_text::SetFontColor, 372 | debug_text::DisplayString, 373 | debug_text::DisplayStringFormatted, 374 | debug_text::DisplayNumber, 375 | &loaderSettings, 376 | &modList, 377 | &RegisterVoice, 378 | &ReplaceTexture, 379 | &UnreplaceFile, 380 | &PushInterpolationFix, 381 | &PopInterpolationFix, 382 | }; 383 | -------------------------------------------------------------------------------- /SA2ModLoader/InterpolationFixes.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include 3 | #include "FunctionHook.h" 4 | #include "InterpolationFixes.h" 5 | 6 | // Euler/Quat conversions: https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles 7 | // Quat lerping: https://stackoverflow.com/a/46187052 8 | 9 | FunctionHook LinearMotionA_t(0x42DF60); 10 | 11 | namespace interpolation 12 | { 13 | bool enabled = false; 14 | } 15 | 16 | void NinjaAngleToQuaternion(NJS_QUATERNION* q, Rotation* ang) 17 | { 18 | auto yaw = (float)NJM_ANG_RAD(ang->z); 19 | auto pitch = (float)NJM_ANG_RAD(ang->y); 20 | auto roll = (float)NJM_ANG_RAD(ang->x); 21 | 22 | auto cy = cosf(yaw * 0.5f); 23 | auto sy = sinf(yaw * 0.5f); 24 | auto cp = cosf(pitch * 0.5f); 25 | auto sp = sinf(pitch * 0.5f); 26 | auto cr = cosf(roll * 0.5f); 27 | auto sr = sinf(roll * 0.5f); 28 | 29 | q->im[0] = sr * cp * cy - cr * sp * sy; 30 | q->im[1] = cr * sp * cy + sr * cp * sy; 31 | q->im[2] = cr * cp * sy - sr * sp * cy; 32 | q->re = cr * cp * cy + sr * sp * sy; 33 | } 34 | 35 | void QuaternionToNinjaAngle(Rotation* angle, NJS_QUATERNION* q) 36 | { 37 | Float sinr_cosp = 2.0f * (q->re * q->im[0] + q->im[1] * q->im[2]); 38 | Float cosr_cosp = 1.0f - 2.0f * (q->im[0] * q->im[0] + q->im[1] * q->im[1]); 39 | angle->x = NJM_RAD_ANG(std::atan2(sinr_cosp, cosr_cosp)); 40 | 41 | Float sinp = 2.0f * (q->re * q->im[1] - q->im[2] * q->im[0]); 42 | if (std::abs(sinp) >= 1.0f) 43 | angle->y = NJM_RAD_ANG(std::copysign(NJD_PI / 2, sinp)); // use 90 degrees if out of range 44 | else 45 | angle->y = NJM_RAD_ANG(std::asin(sinp)); 46 | 47 | Float siny_cosp = 2.0f * (q->re * q->im[2] + q->im[0] * q->im[1]); 48 | Float cosy_cosp = 1.0f - 2.0f * (q->im[1] * q->im[1] + q->im[2] * q->im[2]); 49 | angle->z = NJM_RAD_ANG(std::atan2(siny_cosp, cosy_cosp)); 50 | } 51 | 52 | Float scalorQuaternion(NJS_QUATERNION* q1, NJS_QUATERNION* q2) 53 | { 54 | return q1->im[0] * q2->im[0] + q1->im[1] * q2->im[1] + q1->im[2] * q2->im[2] + q1->re * q2->re; 55 | } 56 | 57 | void negateQuaternion(NJS_QUATERNION* q) 58 | { 59 | q->im[0] = -q->im[0]; 60 | q->im[1] = -q->im[1]; 61 | q->im[2] = -q->im[2]; 62 | q->re = -q->re; 63 | } 64 | 65 | void unitQuaternion(NJS_QUATERNION* q) 66 | { 67 | float l = 1.0f / std::sqrt(scalorQuaternion(q, q)); 68 | q->im[0] = l * q->im[0]; 69 | q->im[1] = l * q->im[1]; 70 | q->im[2] = l * q->im[2]; 71 | q->re = l * q->re; 72 | } 73 | 74 | void lerpQuaternion(NJS_QUATERNION* q, NJS_QUATERNION* a, NJS_QUATERNION* b, Float t) 75 | { 76 | if (scalorQuaternion(a, b) < 0.0f) 77 | { 78 | negateQuaternion(b); 79 | } 80 | 81 | q->im[0] = a->im[0] - t * (a->im[0] - b->im[0]); 82 | q->im[1] = a->im[1] - t * (a->im[1] - b->im[1]); 83 | q->im[2] = a->im[2] - t * (a->im[2] - b->im[2]); 84 | q->re = a->re - t * (a->re - b->re); 85 | } 86 | 87 | void nlerpQuaternion(NJS_QUATERNION* q, NJS_QUATERNION* a, NJS_QUATERNION* b, Float t) 88 | { 89 | lerpQuaternion(q, a, b, t); 90 | unitQuaternion(q); 91 | } 92 | 93 | const intptr_t sub_42C2C0Ptr = 0x42C2C0; 94 | void _nuGetMotionLinearKeys(void* a2, int a1, int a3, float a4, void** a5, void** a6, float* a7) { 95 | __asm { 96 | mov esi, a2 97 | mov edi, a1 98 | push a7 99 | push a6 100 | push a5 101 | push a4 102 | push a3 103 | call sub_42C2C0Ptr 104 | add esp, 5 * 4 105 | } 106 | } 107 | 108 | void __cdecl LinearMotionA_r(const NJS_MKEY_A* key, Uint32 nbkeys, Float frame, Angle3* dst) 109 | { 110 | if (!interpolation::enabled) 111 | { 112 | return LinearMotionA_t.Original(key, nbkeys, frame, dst); 113 | } 114 | 115 | Float rate1; 116 | NJS_MKEY_A* key1; 117 | NJS_MKEY_A* key2; 118 | 119 | _nuGetMotionLinearKeys((void*)key, sizeof(NJS_MKEY_A), nbkeys, frame, 120 | (void**)&key1, (void**)&key2, &rate1); 121 | 122 | NJS_MKEY_A* key_o = key1; 123 | NJS_MKEY_A* key_n = key2; 124 | 125 | Rotation ang_orig = { key_o->key[0], key_o->key[1], key_o->key[2] }; 126 | Rotation ang_next = { key_n->key[0], key_n->key[1], key_n->key[2] }; 127 | 128 | NJS_QUATERNION q1, q2; 129 | NinjaAngleToQuaternion(&q1, &ang_orig); 130 | NinjaAngleToQuaternion(&q2, &ang_next); 131 | 132 | NJS_QUATERNION r; 133 | nlerpQuaternion(&r, &q1, &q2, rate1); 134 | 135 | QuaternionToNinjaAngle(dst, &r); 136 | } 137 | 138 | void interpolation::push() 139 | { 140 | interpolation::enabled = true; 141 | } 142 | 143 | void interpolation::pop() 144 | { 145 | interpolation::enabled = false; 146 | } 147 | 148 | void interpolation::init() 149 | { 150 | LinearMotionA_t.Hook(LinearMotionA_r); 151 | } -------------------------------------------------------------------------------- /SA2ModLoader/InterpolationFixes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #pragma once 4 | 5 | namespace interpolation 6 | { 7 | extern bool enabled; 8 | void init(); 9 | void push(); 10 | void pop(); 11 | } -------------------------------------------------------------------------------- /SA2ModLoader/MediaFns.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * SA2 Mod Loader 3 | * Media functions. 4 | */ 5 | 6 | #include "stdafx.h" 7 | #include "MediaFns.hpp" 8 | #include "FileSystem.h" 9 | 10 | #include "bass_vgmstream.h" 11 | 12 | #include 13 | #include 14 | #include "UsercallFunctionHandler.h" 15 | using std::string; 16 | using std::vector; 17 | 18 | static bool bassinit = false; 19 | static DWORD basschan = 0; 20 | static DWORD voicechan = 0; 21 | /** 22 | * Initialize media playback. 23 | */ 24 | 25 | void BassInit_r() 26 | { 27 | bassinit = !!BASS_Init(-1, 44100, 0, nullptr, nullptr); 28 | } 29 | 30 | static void __stdcall onVoiceEnd(HSYNC handle, DWORD channel, DWORD data, void* user) 31 | { 32 | BASS_ChannelStop(channel); 33 | BASS_StreamFree(channel); 34 | } 35 | 36 | const bool (*sub_4430B0)(signed int a1, signed int a2) = GenerateUsercallWrapper(rEAX, 0x4430B0, rEAX, rEDX); 37 | DataPointer(void**, dword_1A55998, 0x1A55998); 38 | DataPointer(int, dword_1A5599C, 0x1A5599C); 39 | signed int PlayVoice_r(int idk, int num) 40 | { 41 | int v3; // edi 42 | signed int v4; // esi 43 | int v5; // eax 44 | 45 | if (!VoicesEnabled) 46 | { 47 | return -1; 48 | } 49 | 50 | if (bassinit) 51 | { 52 | char path[MAX_PATH]; 53 | if (!VoiceLanguage) 54 | sprintf_s(path, "resource\\gd_pc\\event_adx\\%04d.ahx", num); 55 | else 56 | sprintf_s(path, "resource\\gd_pc\\event_adx_e\\%04d.ahx", num); 57 | 58 | const char* filename = sadx_fileMap.replaceFile(path); 59 | if (FileExists(filename)) 60 | { 61 | voicechan = BASS_VGMSTREAM_StreamCreate(filename, 0); 62 | 63 | if (voicechan == 0) 64 | { 65 | voicechan = BASS_StreamCreateFile(false, filename, 0, 0, 0); 66 | } 67 | 68 | if (voicechan != 0) 69 | { 70 | BASS_ChannelPlay(voicechan, false); 71 | BASS_ChannelSetSync(voicechan, BASS_SYNC_END, 0, onVoiceEnd, nullptr); 72 | return 1; 73 | } 74 | } 75 | } 76 | 77 | v4 = dword_1A5599C; 78 | v3 = 0; 79 | while (1) 80 | { 81 | if (++v4 >= 2) 82 | { 83 | v4 = 0; 84 | } 85 | if (sub_4430B0(v4, (unsigned __int8)idk)) 86 | { 87 | break; 88 | } 89 | if (++v3 >= 3) 90 | { 91 | dword_1A5599C = v4; 92 | return -1; 93 | } 94 | } 95 | v5 = (int)&dword_1A55998[7 * v4]; 96 | *(_DWORD*)(v5 + 40) = num; 97 | *(char*)(v5 + 36) = 1; 98 | *(char*)(v5 + 37) = idk; 99 | dword_1A5599C = v4; 100 | return v4; 101 | } 102 | 103 | void* ReleaseSoundEffects_r() { 104 | BASS_Free(); 105 | return sub_437E90(); 106 | } 107 | 108 | 109 | void Init_AudioBassHook(std::wstring extLibPath) 110 | { 111 | std::wstring bassFolder = extLibPath + L"BASS\\"; 112 | 113 | // If the file doesn't exist, assume it's in the game folder like with the old Manager 114 | if (!FileExists(bassFolder + L"bass_vgmstream.dll")) 115 | bassFolder = L""; 116 | 117 | bool bassDLL = false; 118 | 119 | 120 | std::wstring fullPath = bassFolder + L"bass_vgmstream.dll"; 121 | 122 | bassDLL = LoadLibraryEx(fullPath.c_str(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH); 123 | 124 | if (bassDLL) 125 | { 126 | PrintDebug("Loaded Bass DLLs dependencies\n"); 127 | } 128 | else 129 | { 130 | PrintDebug("Failed to load bass DLL dependencies\n"); 131 | MessageBox(MainWindowHandle, L"Error loading BASS.\n\n" 132 | L"Make sure the Mod Loader is installed properly.", 133 | L"BASS Load Error", MB_OK | MB_ICONERROR); 134 | return; 135 | } 136 | 137 | WriteCall((void*)0x435511, ReleaseSoundEffects_r); 138 | BassInit_r(); 139 | GenerateUsercallHook(PlayVoice_r, rEAX, (intptr_t)PlayVoicePtr, rEDX, stack4); 140 | return; 141 | } -------------------------------------------------------------------------------- /SA2ModLoader/MediaFns.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | VoidFunc(sub_77EC30, 0x77EC30); 4 | FunctionPointer(void*, sub_437E90, (), 0x437E90); 5 | 6 | void Init_AudioBassHook(std::wstring extLibPath); -------------------------------------------------------------------------------- /SA2ModLoader/SA2ModLoader.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {3BF5C1B2-E3A4-43E7-9C2A-E445810834DE} 15 | Win32Proj 16 | SA2ModLoader 17 | 10.0 18 | 19 | 20 | 21 | DynamicLibrary 22 | true 23 | Unicode 24 | v141_xp 25 | 26 | 27 | DynamicLibrary 28 | false 29 | true 30 | Unicode 31 | v141_xp 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | true 45 | $(SolutionDir)bin\ 46 | 47 | 48 | false 49 | $(SolutionDir)bin\ 50 | 51 | 52 | 53 | Use 54 | Level3 55 | Disabled 56 | _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_USRDLL;SA2MODLOADER_EXPORTS;%(PreprocessorDefinitions) 57 | include;..\extlib\bass;..\libmodutils;..\mod-loader-common\ModLoaderCommon;$(DXSDK_DIR)Include 58 | stdcpp17 59 | 60 | 61 | Windows 62 | true 63 | 64 | 65 | dbghelp.lib;bass.lib;bass_vgmstream.lib;Shlwapi.lib;gdiplus.lib;$(OutDir)libmodutils.lib;$(OutDir)ModLoaderCommon.lib;$(DXSDK_DIR)Lib\x86\d3dx9.lib;%(AdditionalDependencies) 66 | ..\extlib\bass 67 | bass.dll;bass_vgmstream.dll 68 | 69 | 70 | 71 | 72 | Level3 73 | Use 74 | MaxSpeed 75 | true 76 | true 77 | _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_WINDOWS;_USRDLL;SA2MODLOADER_EXPORTS;%(PreprocessorDefinitions) 78 | include;..\extlib\bass;..\libmodutils;..\mod-loader-common\ModLoaderCommon;$(DXSDK_DIR)Include 79 | stdcpp17 80 | 81 | 82 | Windows 83 | true 84 | true 85 | true 86 | 87 | 88 | dbghelp.lib;bass.lib;bass_vgmstream.lib;Shlwapi.lib;gdiplus.lib;$(OutDir)libmodutils.lib;$(OutDir)ModLoaderCommon.lib;$(DXSDK_DIR)Lib\x86\d3dx9.lib;%(AdditionalDependencies) 89 | ..\extlib\bass 90 | bass.dll;bass_vgmstream.dll 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | false 137 | 138 | 139 | false 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | Create 155 | Create 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /SA2ModLoader/SA2ModLoader.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {81f04c87-0e83-4a7a-b148-4e555a95ff7e} 18 | 19 | 20 | {9fa0b75d-87a3-4ec7-a3e3-169b1f4c7a86} 21 | 22 | 23 | {efd04e08-c328-487b-b499-25664f498883} 24 | 25 | 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files\include 35 | 36 | 37 | Header Files\include 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files\include 47 | 48 | 49 | Header Files\include 50 | 51 | 52 | Header Files\include 53 | 54 | 55 | Header Files\include 56 | 57 | 58 | Header Files\include 59 | 60 | 61 | Header Files 62 | 63 | 64 | Header Files 65 | 66 | 67 | Header Files 68 | 69 | 70 | Header Files 71 | 72 | 73 | Header Files 74 | 75 | 76 | Header Files\include 77 | 78 | 79 | Header Files\include 80 | 81 | 82 | Header Files 83 | 84 | 85 | Header Files 86 | 87 | 88 | Header Files 89 | 90 | 91 | Header Files\include 92 | 93 | 94 | Header Files 95 | 96 | 97 | Header Files\include 98 | 99 | 100 | Header Files 101 | 102 | 103 | Header Files 104 | 105 | 106 | Header Files 107 | 108 | 109 | Header Files 110 | 111 | 112 | Header Files 113 | 114 | 115 | Header Files\Patches 116 | 117 | 118 | Header Files\Patches 119 | 120 | 121 | 122 | 123 | Source Files 124 | 125 | 126 | Source Files 127 | 128 | 129 | Source Files 130 | 131 | 132 | Source Files 133 | 134 | 135 | Source Files 136 | 137 | 138 | Source Files 139 | 140 | 141 | Source Files 142 | 143 | 144 | Source Files 145 | 146 | 147 | Source Files 148 | 149 | 150 | Source Files 151 | 152 | 153 | Source Files 154 | 155 | 156 | Source Files 157 | 158 | 159 | Source Files 160 | 161 | 162 | Source Files 163 | 164 | 165 | Source Files 166 | 167 | 168 | Source Files 169 | 170 | 171 | Source Files 172 | 173 | 174 | Source Files\Patches 175 | 176 | 177 | Source Files\Patches 178 | 179 | 180 | 181 | 182 | Source Files 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /SA2ModLoader/TextureReplacement.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | struct TexPackEntry 6 | { 7 | uint32_t global_index; 8 | std::string name; 9 | uint32_t width, height; 10 | }; 11 | 12 | void ScanTextureReplaceFolder(const std::string& srcPath, int modIndex); 13 | void ReplaceTexture(const char* pvm_name, const char* tex_name, const char* file_path, uint32_t gbix, uint32_t width, uint32_t height); 14 | 15 | namespace texpack 16 | { 17 | /** 18 | * \brief Parses custom texture index. 19 | * \param path A valid path to a texture pack directory containing index.txt 20 | * \param out A vector to populate. 21 | * \return \c true on success. 22 | */ 23 | bool parse_index(const std::string& path, std::vector& out); 24 | 25 | /** 26 | * \brief Initializes function hooks for texture replacement. 27 | */ 28 | void init(); 29 | } 30 | -------------------------------------------------------------------------------- /SA2ModLoader/config.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "json.hpp" 3 | #include "IniFile.hpp" 4 | #include "FileSystem.h" 5 | #include 6 | 7 | using json = nlohmann::json; 8 | using std::string; 9 | using std::wstring; 10 | using std::vector; 11 | using std::unique_ptr; 12 | 13 | std::wstring currentProfilePath; // Used for crash dumps 14 | vector ModList; 15 | vector GamePatchList; 16 | 17 | std::string GetModName(int index) 18 | { 19 | return ModList.at(index - 1); 20 | } 21 | 22 | int GetModCount() 23 | { 24 | return ModList.size(); 25 | } 26 | 27 | char inilangs[]{ 28 | Language_English, 29 | Language_German, 30 | Language_Spanish, 31 | Language_French, 32 | Language_Italian, 33 | Language_Japanese 34 | }; 35 | 36 | void DisplaySettingsLoadError(wstring file) 37 | { 38 | wstring error = L"Mod Loader settings could not be read. Please run the Mod Manager, save settings and try again.\n\nThe following file was missing: " + file; 39 | MessageBox(nullptr, error.c_str(), L"SA2 Mod Loader", MB_ICONERROR); 40 | OnExit(0, 0, 0); 41 | ExitProcess(0); 42 | } 43 | 44 | bool IsGamePatchEnabled(const char* patchName) 45 | { 46 | return (std::find(std::begin(GamePatchList), std::end(GamePatchList), patchName) != std::end(GamePatchList)); 47 | } 48 | 49 | void ListGamePatches() 50 | { 51 | PrintDebug("Enabling %d game patches:\n", GamePatchList.size()); 52 | for (std::string s : GamePatchList) 53 | { 54 | PrintDebug("Enabled game patch: %s\n", s.c_str()); 55 | } 56 | } 57 | 58 | void LoadModLoaderSettings(LoaderSettings* loaderSettings, std::wstring gamePath) 59 | { 60 | // Get paths for Mod Loader settings and libraries, normally located in 'Sonic Adventure 2\mods\.modloader' 61 | 62 | wstring loaderDataPath = gamePath + L"\\mods\\.modloader\\"; 63 | loaderSettings->ExtLibPath = loaderDataPath + L"extlib\\"; 64 | wstring profilesFolderPath = loaderDataPath + L"profiles\\"; 65 | wstring profilesJsonPath = profilesFolderPath + L"Profiles.json"; 66 | 67 | // If Profiles.json isn't found, assume the old paths system 68 | if (!Exists(profilesJsonPath)) 69 | { 70 | // Check 'Sonic Adventure 2\SAManager\SA2' (portable mode) first 71 | profilesJsonPath = gamePath + L"\\SAManager\\SA2\\Profiles.json"; 72 | if (Exists(profilesJsonPath)) 73 | { 74 | loaderDataPath = gamePath + L"\\SAManager\\"; 75 | } 76 | // If that doesn't exist either, assume the settings are in 'AppData\Local\SAManager' 77 | else 78 | { 79 | WCHAR appDataLocalBuf[MAX_PATH]; 80 | // Get the LocalAppData folder and check if it has the profiles json 81 | if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appDataLocalBuf))) 82 | { 83 | wstring appDataLocalPath(appDataLocalBuf); 84 | profilesJsonPath = appDataLocalPath + L"\\SAManager\\SA2\\Profiles.json"; 85 | if (Exists(profilesJsonPath)) 86 | { 87 | loaderDataPath = appDataLocalPath + L"\\SAManager\\"; 88 | } 89 | // If it still can't be found, display an error message 90 | else 91 | DisplaySettingsLoadError(gamePath + L"\\mods\\.modloader\\Profiles.json"); 92 | } 93 | else 94 | { 95 | MessageBox(nullptr, L"Unable to retrieve local AppData path.", L"SA2 Mod Loader", MB_ICONERROR); 96 | OnExit(0, 0, 0); 97 | ExitProcess(0); 98 | } 99 | } 100 | // If Profiles.json was found, set old paths 101 | loaderSettings->ExtLibPath = loaderDataPath + L"extlib\\"; 102 | profilesFolderPath = loaderDataPath + L"SA2\\"; 103 | } 104 | 105 | // Load profiles JSON file 106 | std::ifstream ifs(profilesJsonPath); 107 | json json_profiles = json::parse(ifs); 108 | ifs.close(); 109 | 110 | // Get current profile index 111 | int ind_profile = json_profiles.value("ProfileIndex", 0); 112 | 113 | // Get current profile filename 114 | json proflist = json_profiles["ProfilesList"]; 115 | std::string profname = proflist.at(ind_profile)["Filename"]; 116 | 117 | // Convert profile name from UTF8 stored in JSON to wide string 118 | int count = MultiByteToWideChar(CP_UTF8, 0, profname.c_str(), profname.length(), NULL, 0); 119 | std::wstring profname_w(count, 0); 120 | MultiByteToWideChar(CP_UTF8, 0, profname.c_str(), profname.length(), &profname_w[0], count); 121 | 122 | // Load the current profile 123 | currentProfilePath = profilesFolderPath + profname_w; 124 | if (!Exists(currentProfilePath)) 125 | { 126 | DisplaySettingsLoadError(currentProfilePath); 127 | } 128 | 129 | 130 | std::ifstream ifs_p(currentProfilePath); 131 | json json_config = json::parse(ifs_p); 132 | int settingsVersion = json_config.value("SettingsVersion", 0); 133 | 134 | // Graphics settings 135 | json json_graphics = json_config["Graphics"]; 136 | const uint8_t BFOWTF = json_graphics.value("ScreenMode", 1); 137 | loaderSettings->ScreenMode = BFOWTF; 138 | loaderSettings->ScreenNum = json_graphics.value("SelectedScreen", 0); 139 | loaderSettings->HorizontalResolution = json_graphics.value("HorizontalResolution", 640); 140 | loaderSettings->VerticalResolution = json_graphics.value("VerticalResolution", 480); 141 | loaderSettings->PauseWhenInactive = json_graphics.value("EnablePauseOnInactive", true); 142 | loaderSettings->CustomWindowSize = json_graphics.value("EnableCustomWindow", false); 143 | loaderSettings->WindowWidth = json_graphics.value("CustomWindowWidth", 640); 144 | loaderSettings->WindowHeight = json_graphics.value("CustomWindowHeight", 480); 145 | loaderSettings->ResizableWindow = json_graphics.value("EnableResizableWindow", true); 146 | loaderSettings->StretchToWindow = json_graphics.value("StretchToWindow", false); 147 | loaderSettings->MaintainAspectRatio = loaderSettings->KeepAspectWhenResizing = !loaderSettings->StretchToWindow; 148 | loaderSettings->SkipIntro = json_graphics.value("SkipIntro", false); 149 | loaderSettings->DisableBorderImage = json_graphics.value("DisableBorderImage", false); 150 | 151 | // Game Patches: 152 | // V1: `Patches` as a struct with bools that have hardcoded names (converted to bool values in LoaderSettings) 153 | // V2: `EnabledGamePatches` as a list of enabled patch names as strings 154 | // V3: `Patches` as a dictionary of strings and bools 155 | // V1 is compatible with V3 because it's stored with the same formatting in the JSON file 156 | 157 | // Process V2 first 158 | if (json_config.contains("EnabledGamePatches")) 159 | { 160 | json json_patches_list = json_config["EnabledGamePatches"]; 161 | for (unsigned int i = 1; i <= json_patches_list.size(); i++) 162 | { 163 | std::string patch_name = json_patches_list.at(i - 1); 164 | GamePatchList.push_back(patch_name); 165 | } 166 | } 167 | // Process V1/V3 168 | if (json_config.contains("Patches")) 169 | { 170 | json json_patches = json_config["Patches"]; 171 | for (json::iterator it = json_patches.begin(); it != json_patches.end(); ++it) 172 | { 173 | std::string patch_name = it.key(); 174 | if (it.value() == true) 175 | { 176 | // Check if it isn't on the list already (legacy patches can be there) 177 | if (std::find(std::begin(GamePatchList), std::end(GamePatchList), patch_name) == std::end(GamePatchList)); 178 | GamePatchList.push_back(patch_name); 179 | } 180 | } 181 | // Update the old LoaderSettings values for compatibility 182 | loaderSettings->FramerateLimiter = json_patches.value("FramerateLimiter", true); 183 | loaderSettings->DisableExitPrompt = json_patches.value("DisableExitPrompt", true); 184 | loaderSettings->SyncLoad = json_patches.value("SyncLoad", true); 185 | loaderSettings->ExtendVertexBuffer = json_patches.value("ExtendVertexBuffer", true); 186 | loaderSettings->EnvMapFix = json_patches.value("EnvMapFix", true); 187 | loaderSettings->ScreenFadeFix = json_patches.value("ScreenFadeFix", true); 188 | loaderSettings->CECarFix = json_patches.value("CECarFix", true); 189 | loaderSettings->ParticlesFix = json_patches.value("ParticlesFix", true); 190 | } 191 | 192 | // Debug settings 193 | json json_debug = json_config["DebugSettings"]; 194 | loaderSettings->DebugConsole = json_debug.value("EnableDebugConsole", false); 195 | loaderSettings->DebugScreen = json_debug.value("EnableDebugScreen", false); 196 | loaderSettings->DebugFile = json_debug.value("EnableDebugFile", false); 197 | loaderSettings->DebugCrashLog = json_debug.value("EnableDebugCrashLog", true); 198 | 199 | // Testspawn settings 200 | json json_testspawn = json_config["TestSpawn"]; 201 | loaderSettings->TextLanguage = json_testspawn.value("GameTextLanguage", 1); 202 | loaderSettings->VoiceLanguage = json_testspawn.value("GameVoiceLanguage", 1); 203 | 204 | // Mods 205 | json json_mods = json_config["EnabledMods"]; 206 | for (unsigned int i = 1; i <= json_mods.size(); i++) 207 | { 208 | std::string mod_fname = json_mods.at(i - 1); 209 | ModList.push_back(mod_fname); 210 | } 211 | } -------------------------------------------------------------------------------- /SA2ModLoader/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void LoadModLoaderSettings(LoaderSettings* loaderSettings, std::wstring gamePath); 4 | int GetModCount(); 5 | std::string GetModName(int index); 6 | bool IsGamePatchEnabled(const char* patchName); 7 | 8 | extern std::wstring currentProfilePath; -------------------------------------------------------------------------------- /SA2ModLoader/cpp.hint: -------------------------------------------------------------------------------- 1 | // Data pointer and array declarations. 2 | #define DataPointer(type, name, address) \ 3 | static type &name = *(type *)address 4 | #define DataArray(type, name, address, length) \ 5 | static type *const name = (type *)address; static const int name##_Length = length 6 | 7 | // Function pointer declarations. 8 | #define FunctionPointer(RETURN_TYPE, NAME, ARGS, ADDRESS) \ 9 | static RETURN_TYPE (__cdecl *const NAME)ARGS = (RETURN_TYPE (__cdecl *)ARGS)ADDRESS 10 | #define StdcallFunctionPointer(RETURN_TYPE, NAME, ARGS, ADDRESS) \ 11 | static RETURN_TYPE (__stdcall *const NAME)ARGS = (RETURN_TYPE (__stdcall *)ARGS)ADDRESS 12 | #define FastcallFunctionPointer(RETURN_TYPE, NAME, ARGS, ADDRESS) \ 13 | static RETURN_TYPE (__fastcall *const NAME)ARGS = (RETURN_TYPE (__fastcall *)ARGS)ADDRESS 14 | #define ThiscallFunctionPointer(RETURN_TYPE, NAME, ARGS, ADDRESS) \ 15 | static RETURN_TYPE (__thiscall *const NAME)ARGS = (RETURN_TYPE (__thiscall *)ARGS)ADDRESS 16 | #define VoidFunc(NAME, ADDRESS) FunctionPointer(void,NAME,(void),ADDRESS) 17 | 18 | // Non-static FunctionPointer. 19 | // If declaring a FunctionPointer within a function, use this one instead. 20 | // Otherwise, the program will crash on Windows XP. 21 | #define NonStaticFunctionPointer(RETURN_TYPE, NAME, ARGS, ADDRESS) \ 22 | RETURN_TYPE (__cdecl *const NAME)ARGS = (RETURN_TYPE (__cdecl *)ARGS)ADDRESS 23 | 24 | #define ObjectFunc(NAME, ADDRESS) FunctionPointer(void,NAME,(ObjectMaster *obj),ADDRESS) -------------------------------------------------------------------------------- /SA2ModLoader/direct3d.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "magic.h" 3 | #include "Events.h" 4 | 5 | #include "DebugText.h" 6 | 7 | #include 8 | #include 9 | 10 | #include "direct3d.h" 11 | 12 | // Direct3D utilties and hooks for window stuff and frame limiter. 13 | 14 | using namespace std::chrono; 15 | 16 | using FrameRatio = duration>; 17 | 18 | static bool enable_frame_limit = false; 19 | static auto frame_start = system_clock::now(); 20 | static duration present_time = {}; 21 | 22 | static const auto frame_ratio = FrameRatio(1); 23 | static const auto frame_portion_ms = duration_cast(frame_ratio) - milliseconds(1); 24 | 25 | static HWND DrawWindow = NULL; 26 | 27 | static void RunFrameLimiter() 28 | { 29 | if (!enable_frame_limit) 30 | return; 31 | 32 | if (present_time < frame_ratio) 33 | { 34 | auto now = system_clock::now(); 35 | const milliseconds delta = duration_cast(now - frame_start); 36 | 37 | if (delta < frame_ratio) 38 | { 39 | // sleep for a portion of the frame time to free up cpu time 40 | std::this_thread::sleep_for(frame_portion_ms - delta); 41 | 42 | while ((now = system_clock::now()) - frame_start < frame_ratio) 43 | { 44 | // spin for the remainder of the time 45 | } 46 | } 47 | } 48 | 49 | frame_start = system_clock::now(); 50 | } 51 | 52 | static HRESULT __fastcall Present_r(Magic::RenderCore::RenderDevice_DX9* dev) 53 | { 54 | HRESULT result; 55 | if (enable_frame_limit) 56 | { 57 | // This is done to avoid vsync issues. 58 | const auto start = std::chrono::system_clock::now(); 59 | result = dev->m_pD3DDevice->Present(nullptr, nullptr, DrawWindow, nullptr); 60 | present_time = std::chrono::system_clock::now() - start; 61 | } 62 | else 63 | { 64 | result = dev->m_pD3DDevice->Present(nullptr, nullptr, DrawWindow, nullptr); 65 | } 66 | return result; 67 | } 68 | 69 | static void __fastcall BeginScene_r(Magic::RenderCore::RenderDevice_DX9* dev) 70 | { 71 | dev->m_pD3DDevice->BeginScene(); 72 | RunFrameLimiter(); 73 | RaiseEvents(onRenderSceneStart); 74 | } 75 | 76 | static void __fastcall EndScene_r(Magic::RenderCore::RenderDevice_DX9* dev) 77 | { 78 | RaiseEvents(onRenderSceneEnd); 79 | dev->m_pD3DDevice->EndScene(); 80 | } 81 | 82 | void direct3d::reset_device() 83 | { 84 | RaiseEvents(modRenderDeviceLost); 85 | g_pRenderDevice->__vftable->ResetRenderDeviceInitInfo(g_pRenderDevice, &g_pRenderDevice->m_InitInfo, &DeviceLostFunc, &DeviceResetFunc); 86 | RaiseEvents(modRenderDeviceReset); 87 | } 88 | 89 | void direct3d::change_resolution(int w, int h) 90 | { 91 | change_resolution(w, h, g_pRenderDevice->m_pDeviceCreator->m_D3DPP.Windowed); 92 | } 93 | 94 | void direct3d::change_resolution(int w, int h, bool windowed) 95 | { 96 | // Make sure at least one parameter is different before resetting the device 97 | auto& pp = g_pRenderDevice->m_pDeviceCreator->m_D3DPP; 98 | if (pp.BackBufferWidth != w || pp.BackBufferHeight != h || pp.Windowed != (BOOL)windowed) 99 | { 100 | HorizontalResolution = static_cast(w); 101 | VerticalResolution = static_cast(h); 102 | g_pRenderDevice->m_InitInfo.m_BackBufferWidth = w; 103 | g_pRenderDevice->m_InitInfo.m_BackBufferHeight = h; 104 | pp.BackBufferWidth = static_cast(w); 105 | pp.BackBufferHeight = static_cast(h); 106 | reset_device(); 107 | } 108 | } 109 | 110 | void direct3d::change_dest_window(HWND hwnd) 111 | { 112 | DrawWindow = hwnd; 113 | } 114 | 115 | void direct3d::enable_frame_limiter() 116 | { 117 | enable_frame_limit = true; 118 | } 119 | 120 | void direct3d::init() 121 | { 122 | WriteJump(Magic::RenderCore::Present, Present_r); 123 | WriteJump(Magic::RenderCore::BeginScene, BeginScene_r); 124 | WriteJump(Magic::RenderCore::EndScene, EndScene_r); 125 | } -------------------------------------------------------------------------------- /SA2ModLoader/direct3d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace direct3d 4 | { 5 | void reset_device(); 6 | void change_resolution(int w, int h); 7 | void change_resolution(int w, int h, bool windowed); 8 | void change_dest_window(HWND hwnd); 9 | void enable_frame_limiter(); 10 | void init(); 11 | } -------------------------------------------------------------------------------- /SA2ModLoader/include/MemAccess.h: -------------------------------------------------------------------------------- 1 | /** 2 | * SA2 Mod Loader. 3 | * Memory access inline functions. 4 | */ 5 | 6 | #ifndef MODLOADER_MEMACCESS_H 7 | #define MODLOADER_MEMACCESS_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | // Utility Functions 14 | 15 | /** 16 | * Get the number of elements in an array. 17 | * @return Number of elements in the array. 18 | */ 19 | template 20 | static constexpr Tret LengthOfArray(const T(&)[N]) noexcept 21 | { 22 | return (Tret)N; 23 | } 24 | 25 | /** 26 | * Get the size of an array. 27 | * @return Size of the array, in bytes. 28 | */ 29 | template 30 | static constexpr Tret SizeOfArray(const T(&)[N]) noexcept 31 | { 32 | return (Tret)(N * sizeof(T)); 33 | } 34 | 35 | // Macros for functions that need both an array 36 | // and the array length or size. 37 | #define arrayptrandlengthT(data,T) data, LengthOfArray(data) 38 | #define arraylengthandptrT(data,T) LengthOfArray(data), data 39 | #define arrayptrandsizeT(data,T) data, SizeOfArray(data) 40 | #define arraysizeandptrT(data,T) SizeOfArray(data), data 41 | 42 | // Macros for functions that need both an array 43 | // and the array length or size. 44 | #define arrayptrandlength(data) data, LengthOfArray(data) 45 | #define arraylengthandptr(data) LengthOfArray(data), data 46 | #define arrayptrandsize(data) data, SizeOfArray(data) 47 | #define arraysizeandptr(data) SizeOfArray(data), data 48 | 49 | #ifndef WIN32_LEAN_AND_MEAN 50 | #define WIN32_LEAN_AND_MEAN 51 | #endif 52 | #include 53 | 54 | static inline BOOL WriteData(void* writeaddress, const void* data, SIZE_T datasize) 55 | { 56 | DWORD oldprot; 57 | VirtualProtect(writeaddress, datasize, PAGE_EXECUTE_WRITECOPY, &oldprot); 58 | memcpy(writeaddress, data, datasize); 59 | return true; 60 | } 61 | 62 | template 63 | static inline BOOL WriteData(T const* writeaddress, const T data) 64 | { 65 | return WriteData((void*)writeaddress, (void*)&data, (SIZE_T)sizeof(data)); 66 | } 67 | 68 | template 69 | static inline BOOL WriteData(T* writeaddress, const T& data) 70 | { 71 | return WriteData(writeaddress, &data, sizeof(data)); 72 | } 73 | 74 | template 75 | static inline BOOL WriteData(void* writeaddress, const T(&data)[N]) 76 | { 77 | return WriteData(writeaddress, data, SizeOfArray(data)); 78 | } 79 | 80 | /** 81 | * Write a repeated byte to an arbitrary address. 82 | * @param address [in] Address. 83 | * @param data [in] Byte to write. 84 | * @param byteswritten [out, opt] Number of bytes written. 85 | * @return Nonzero on success; 0 on error (check GetLastError()). 86 | */ 87 | template 88 | static inline BOOL WriteData(void* address, uint8_t data) 89 | { 90 | DWORD oldprot; 91 | VirtualProtect(address, count, PAGE_EXECUTE_WRITECOPY, &oldprot); 92 | memset(address, data, count); 93 | return true; 94 | } 95 | 96 | #if (defined(__i386__) || defined(_M_IX86)) && \ 97 | !(defined(__x86_64__) || defined(_M_X64)) 98 | 99 | // JMP/CALL DWORD relative opcode union. 100 | #pragma pack(1) 101 | union JmpCallDwordRel { 102 | struct { 103 | uint8_t opcode; 104 | int32_t address; 105 | }; 106 | uint8_t u8[5]; 107 | 108 | JmpCallDwordRel() {} 109 | 110 | JmpCallDwordRel(bool isCall, intptr_t src, intptr_t dst) 111 | { 112 | opcode = isCall ? 0xE8 : 0xE9; 113 | address = dst - (src + 5); 114 | } 115 | 116 | JmpCallDwordRel(bool isCall, void* src, void* dst) 117 | { 118 | opcode = isCall ? 0xE8 : 0xE9; 119 | address = (intptr_t)dst - ((intptr_t)src + 5); 120 | } 121 | }; 122 | #pragma pack() 123 | 124 | /** 125 | * Write a JMP instruction to an arbitrary address. 126 | * @param writeaddress Address to insert the JMP instruction. 127 | * @param funcaddress Address to JMP to. 128 | * @return Nonzero on success; 0 on error (check GetLastError()). 129 | */ 130 | static inline BOOL WriteJump(void* writeaddress, void* funcaddress) 131 | { 132 | JmpCallDwordRel data(false, writeaddress, funcaddress); 133 | return WriteData(writeaddress, data.u8); 134 | } 135 | 136 | /** 137 | * Write a CALL instruction to an arbitrary address. 138 | * @param writeaddress Address to insert the CALL instruction. 139 | * @param funcaddress Address to CALL. 140 | * @return Nonzero on success; 0 on error (check GetLastError()). 141 | */ 142 | static inline BOOL WriteCall(void* writeaddress, void* funcaddress) 143 | { 144 | JmpCallDwordRel data(true, writeaddress, funcaddress); 145 | return WriteData(writeaddress, data.u8); 146 | } 147 | 148 | #endif 149 | 150 | // Data pointer and array declarations. 151 | #define DataPointer(type, name, address) \ 152 | static type &name = *(type *)address 153 | #define DataArray(type, name, address, len) \ 154 | static DataArray_t name 155 | 156 | template 157 | struct DataArray_t final 158 | { 159 | typedef T value_type; 160 | typedef size_t size_type; 161 | typedef ptrdiff_t difference_type; 162 | typedef value_type& reference; 163 | typedef const value_type& const_reference; 164 | typedef value_type* pointer; 165 | typedef const value_type* const_pointer; 166 | typedef pointer iterator; 167 | typedef const_pointer const_iterator; 168 | typedef std::reverse_iterator reverse_iterator; 169 | typedef std::reverse_iterator const_reverse_iterator; 170 | 171 | DataArray_t() = default; // have to declare default constructor 172 | DataArray_t(const DataArray_t&) = delete; // object cannot be copied, prevents accidentally using DataArray in a function call 173 | DataArray_t(const DataArray_t&&) = delete; // object cannot be moved 174 | 175 | // Gets the underlying data for the array. 176 | constexpr pointer data() const noexcept { return reinterpret_cast(addr); } 177 | // Gets the underlying data for the array. 178 | constexpr const_pointer cdata() const noexcept { return reinterpret_cast(addr); } 179 | 180 | // Checks if the array is empty (no elements). 181 | constexpr bool empty() const noexcept { return len == 0; } 182 | 183 | // Gets the size of the array, in elements. 184 | constexpr size_type size() const noexcept { return len; } 185 | 186 | // Gets the maximum size of the array, in elements. 187 | constexpr size_type max_size() const noexcept { return len; } 188 | 189 | constexpr pointer operator&() const noexcept { return data(); } 190 | 191 | constexpr operator pointer() const noexcept { return data(); } 192 | 193 | // Gets an item from the array, with bounds checking. 194 | constexpr reference at(size_type i) 195 | { 196 | if (i < len) 197 | return data()[i]; 198 | throw std::out_of_range("Data access out of range."); 199 | } 200 | 201 | // Gets an item from the array, with bounds checking. 202 | constexpr const_reference at(size_type i) const 203 | { 204 | if (i < len) 205 | return cdata()[i]; 206 | throw std::out_of_range("Data access out of range."); 207 | } 208 | 209 | template 210 | // Gets an item from the array, with compile-time bounds checking. 211 | constexpr reference get() noexcept 212 | { 213 | static_assert(I < len, "index is within bounds"); 214 | return data()[I]; 215 | } 216 | 217 | template 218 | // Gets an item from the array, with compile-time bounds checking. 219 | constexpr const_reference get() const noexcept 220 | { 221 | static_assert(I < len, "index is within bounds"); 222 | return cdata()[I]; 223 | } 224 | 225 | // Gets the first item in the array. 226 | constexpr reference front() { return *data(); } 227 | // Gets the first item in the array. 228 | constexpr const_reference front() const { return *cdata(); } 229 | 230 | // Gets the last item in the array. 231 | constexpr reference back() { return data()[len - 1]; } 232 | // Gets the last item in the array. 233 | constexpr const_reference back() const { return cdata()[len - 1]; } 234 | 235 | // Gets an iterator to the beginning of the array. 236 | constexpr iterator begin() noexcept { return data(); } 237 | // Gets an iterator to the beginning of the array. 238 | constexpr const_iterator begin() const noexcept { return cdata(); } 239 | // Gets an iterator to the beginning of the array. 240 | constexpr const_iterator cbegin() const noexcept { return cdata(); } 241 | 242 | // Gets an iterator to the end of the array. 243 | constexpr iterator end() noexcept { return data() + len; } 244 | // Gets an iterator to the end of the array. 245 | constexpr const_iterator end() const noexcept { return cdata() + len; } 246 | // Gets an iterator to the end of the array. 247 | constexpr const_iterator cend() const noexcept { return cdata() + len; } 248 | 249 | // Gets a reverse iterator to the beginning of the array. 250 | constexpr reverse_iterator rbegin() noexcept { return data() + len; } 251 | // Gets a reverse iterator to the beginning of the array. 252 | constexpr const_reverse_iterator rbegin() const noexcept { return cdata() + len; } 253 | // Gets a reverse iterator to the beginning of the array. 254 | constexpr const_reverse_iterator crbegin() const noexcept { return cdata() + len; } 255 | 256 | // Gets a reverse iterator to the end of the array. 257 | constexpr reverse_iterator rend() noexcept { return data(); } 258 | // Gets a reverse iterator to the end of the array. 259 | constexpr const_reverse_iterator rend() const noexcept { return cdata(); } 260 | // Gets a reverse iterator to the end of the array. 261 | constexpr const_reverse_iterator crend() const noexcept { return cdata(); } 262 | }; 263 | 264 | // Function pointer declarations. 265 | #define FunctionPointer(RETURN_TYPE, NAME, ARGS, ADDRESS) \ 266 | static RETURN_TYPE (__cdecl *const NAME)ARGS = (RETURN_TYPE (__cdecl *)ARGS)ADDRESS 267 | #define StdcallFunctionPointer(RETURN_TYPE, NAME, ARGS, ADDRESS) \ 268 | static RETURN_TYPE (__stdcall *const NAME)ARGS = (RETURN_TYPE (__stdcall *)ARGS)ADDRESS 269 | #define FastcallFunctionPointer(RETURN_TYPE, NAME, ARGS, ADDRESS) \ 270 | static RETURN_TYPE (__fastcall *const NAME)ARGS = (RETURN_TYPE (__fastcall *)ARGS)ADDRESS 271 | #define ThiscallFunctionPointer(RETURN_TYPE, NAME, ARGS, ADDRESS) \ 272 | static RETURN_TYPE (__thiscall *const NAME)ARGS = (RETURN_TYPE (__thiscall *)ARGS)ADDRESS 273 | #define VoidFunc(NAME, ADDRESS) FunctionPointer(void,NAME,(void),ADDRESS) 274 | 275 | // Non-static FunctionPointer. 276 | // If declaring a FunctionPointer within a function, use this one instead. 277 | // Otherwise, the program will crash on Windows XP. 278 | #define NonStaticFunctionPointer(RETURN_TYPE, NAME, ARGS, ADDRESS) \ 279 | RETURN_TYPE (__cdecl *const NAME)ARGS = (RETURN_TYPE (__cdecl *)ARGS)ADDRESS 280 | 281 | #define patchdecl(address,data) { (void*)address, arrayptrandsize(data) } 282 | #define ptrdecl(address,data) { (void*)address, (void*)data } 283 | 284 | #endif /* MODLOADER_MEMACCESS_H */ -------------------------------------------------------------------------------- /SA2ModLoader/include/SA2ModInfo.h: -------------------------------------------------------------------------------- 1 | /** 2 | * SA2 Mod Loader. 3 | * Mod metadata structures. 4 | */ 5 | 6 | #ifndef SA2MODLOADER_SA2MODINFO_H 7 | #define SA2MODLOADER_SA2MODINFO_H 8 | 9 | #include "SA2Structs.h" 10 | 11 | static const int ModLoaderVer = 15; 12 | 13 | struct PatchInfo 14 | { 15 | void* address; 16 | const void* data; 17 | int datasize; 18 | }; 19 | 20 | struct PatchList 21 | { 22 | const PatchInfo* Patches; 23 | int Count; 24 | }; 25 | 26 | struct PointerInfo 27 | { 28 | void* address; 29 | void* data; 30 | }; 31 | 32 | struct PointerList 33 | { 34 | const PointerInfo* Pointers; 35 | int Count; 36 | }; 37 | 38 | #define patchdecl(address,data) { (void*)address, arrayptrandsize(data) } 39 | #define ptrdecl(address,data) { (void*)address, (void*)data } 40 | 41 | struct LoaderSettings 42 | { 43 | bool DebugConsole; 44 | bool DebugScreen; 45 | bool DebugFile; 46 | bool DebugCrashLog; 47 | bool PauseWhenInactive; 48 | bool DisableExitPrompt; 49 | int ScreenNum; 50 | bool BorderlessWindow; 51 | bool FullScreen; 52 | bool SkipIntro; 53 | bool SyncLoad; 54 | int HorizontalResolution; 55 | int VerticalResolution; 56 | int VoiceLanguage; 57 | int TextLanguage; 58 | bool CustomWindowSize; 59 | int WindowWidth; 60 | int WindowHeight; 61 | bool ResizableWindow; 62 | bool MaintainAspectRatio; // Deprecated, use StretchToWindow 63 | bool FramerateLimiter; 64 | int TestSpawnLevel; 65 | int TestSpawnCharacter; 66 | int TestSpawnPlayer2; 67 | bool TestSpawnPositionEnabled; 68 | int TestSpawnX; 69 | int TestSpawnY; 70 | int TestSpawnZ; 71 | int TestSpawnRotation; 72 | int TestSpawnEvent; 73 | int TestSpawnSaveID; 74 | bool ExtendVertexBuffer; 75 | bool EnvMapFix; 76 | bool ScreenFadeFix; 77 | bool CECarFix; 78 | bool ParticlesFix; 79 | bool KeepAspectWhenResizing; // Deprecated, use StretchToWindow 80 | int ScreenMode; // Window Mode (Windowed, Fullscreen, Borderless Fullscren, or Custom Window); 81 | bool DisableBorderImage; // Requires version >= 14. 82 | bool StretchToWindow; // Stretch content to the window instead of respecting aspect ratio. Requires version >= 14. 83 | // Paths 84 | std::wstring ExtLibPath; // Location of the 'extlib' folder; requires version >= 15 85 | }; 86 | 87 | struct ModDependency 88 | { 89 | const char* ID; 90 | const char* Folder; 91 | const char* Name; 92 | const char* Link; 93 | }; 94 | 95 | struct ModDepsList 96 | { 97 | typedef ModDependency value_type; 98 | typedef int size_type; 99 | typedef const value_type& reference; 100 | typedef const value_type* pointer; 101 | typedef pointer iterator; 102 | 103 | pointer data; 104 | size_type size; 105 | 106 | // Retrieves an iterator to the start of the list (enables range-based for). 107 | iterator begin() 108 | { 109 | return data; 110 | } 111 | 112 | // Retrieves an iterator to the end of the list (enables range-based for). 113 | iterator end() 114 | { 115 | return data + size; 116 | } 117 | 118 | reference operator [](size_type pos) 119 | { 120 | return data[pos]; 121 | } 122 | }; 123 | 124 | struct Mod 125 | { 126 | const char* Name; 127 | const char* Author; 128 | const char* Description; 129 | const char* Version; 130 | const char* Folder; 131 | const char* ID; 132 | HMODULE DLLHandle; 133 | bool MainSaveRedirect; 134 | bool ChaoSaveRedirect; 135 | const ModDepsList Dependencies; 136 | 137 | template 138 | T GetDllExport(const char* name) const 139 | { 140 | if (!DLLHandle) 141 | return nullptr; 142 | return reinterpret_cast(GetProcAddress(DLLHandle, name)); 143 | } 144 | }; 145 | 146 | struct ModList 147 | { 148 | typedef Mod value_type; 149 | typedef int size_type; 150 | typedef const value_type& reference; 151 | typedef const value_type* pointer; 152 | typedef pointer iterator; 153 | 154 | // Retrieves an iterator to the start of the list (enables range-based for). 155 | iterator(*begin)(); 156 | // Retrieves an iterator to the end of the list (enables range-based for). 157 | iterator(*end)(); 158 | // Retrieves the item at position pos. 159 | reference(*at)(size_type pos); 160 | // Retrieves a pointer to the start of the list. 161 | pointer(*data)(); 162 | // Retrieves the number of items in the list. 163 | size_type(*size)(); 164 | // Find a mod by its ID. 165 | iterator(*find)(const char* id); 166 | // Find a mod by its name. 167 | iterator(*find_by_name)(const char* name); 168 | // Find a mod by its folder. 169 | iterator(*find_by_folder)(const char* folder); 170 | // Find a mod by its DLL handle. 171 | iterator(*find_by_dll)(HMODULE handle); 172 | 173 | reference operator [](size_type pos) 174 | { 175 | return at(pos); 176 | } 177 | }; 178 | 179 | #undef ReplaceFile // WinAPI function 180 | struct HelperFunctions 181 | { 182 | // The version of the structure. 183 | int Version; 184 | // Registers a start position for a character. 185 | void(__cdecl* RegisterStartPosition)(unsigned char character, const StartPosition& position); 186 | // Clears the list of registered start positions for a character. 187 | void(__cdecl* ClearStartPositionList)(unsigned char character); 188 | // Registers a 2P intro position for a character. 189 | void(__cdecl* Register2PIntroPosition)(unsigned char character, const LevelEndPosition& position); 190 | // Clears the list of registered 2P intro positions for a character. 191 | void(__cdecl* Clear2PIntroPositionList)(unsigned char character); 192 | // Returns the path where main game save files are stored. 193 | // Requires version >= 4. 194 | const char* (__cdecl* GetMainSavePath)(); 195 | // Returns the path where Chao save files are stored. 196 | // Requires version >= 4. 197 | const char* (__cdecl* GetChaoSavePath)(); 198 | // Registers an end position for a character. 199 | // Requires version >= 5. 200 | void(__cdecl* RegisterEndPosition)(unsigned char character, const StartPosition& position); 201 | // Clears the list of registered end positions for a character. 202 | // Requires version >= 5. 203 | void(__cdecl* ClearEndPositionList)(unsigned char character); 204 | // Registers an end position for missions 2 and 3 for a character. 205 | // Requires version >= 5. 206 | void(__cdecl* RegisterMission23EndPosition)(unsigned char character, const LevelEndPosition& position); 207 | // Clears the list of registered end positions for missions 2 and 3 for a character. 208 | // Requires version >= 5. 209 | void(__cdecl* ClearMission23EndPositionList)(unsigned char character); 210 | // Replaces data exported from the Data DLL with your own data. 211 | // Requires version >= 6. 212 | void(__cdecl* HookExport)(LPCSTR exportName, const void* newdata); 213 | /** 214 | * @brief Gets the real path to a replaceable file. 215 | * 216 | * If your mod contains files in its SYSTEM folder that it loads manually, 217 | * you can use this function to retrieve the full path to the file. This 218 | * allows other mods to replace this file without any extra work from you. 219 | * Requires version >= 7. 220 | * 221 | * @param path The file path (e.g "resource\\gd_PC\\my_cool_file.bin") 222 | * @return The replaced path to the file. 223 | */ 224 | const char* (__cdecl* GetReplaceablePath)(const char* path); 225 | // Replaces the source file with the destination file. 226 | // Requires version >= 7. 227 | void(__cdecl* ReplaceFile)(const char* src, const char* dst); 228 | // Sets the window title. 229 | // Requires version >= 7. 230 | void(__cdecl* SetWindowTitle)(const wchar_t* title); 231 | // Sets the size of the debug font, defaults to 12. 232 | // Requires version >= 8 233 | void(__cdecl* SetDebugFontSize)(float size); 234 | // Sets the argb color of the debug font, defaults to 0xFFBFBFBF. 235 | // Requires version >= 8 236 | void(__cdecl* SetDebugFontColor)(int color); 237 | // Displays a string on screen at a specific location (using NJM_LOCATION) 238 | // Example: DisplayDebugString(NJM_LOCATION(x, y), "string"); 239 | // Requires version >= 8 240 | void(__cdecl* DisplayDebugString)(int loc, const char* str); 241 | // Displays a formatted string on screen at a specific location (using NJM_LOCATION) 242 | // Requires version >= 8 243 | void(__cdecl* DisplayDebugStringFormatted)(int loc, const char* Format, ...); 244 | // Displays a number on screen at a specific location (using NJM_LOCATION) 245 | // If the number of digits is superior, it will add leading zeroes. 246 | // Example: DisplayDebugNumber(NJM_LOCATION(x, y), 123, 5); will display 00123. 247 | // Requires version >= 8 248 | void(__cdecl* DisplayDebugNumber)(int loc, int value, int numdigits); 249 | 250 | // The settings that the mod loader was initialized with. 251 | // Requires version >= 9. 252 | const LoaderSettings* LoaderSettings; 253 | 254 | // API for listing information on loaded mods. 255 | // Requires version >= 9. 256 | const ModList* Mods; 257 | 258 | /** 259 | * @brief Registers an ID for a new voice. 260 | * Requires version >= 10. 261 | * 262 | * @param fileJP: The path to the audio file to play for Japanese. 263 | * @param fileEN: The path to the audio file to play for English. 264 | * @return The ID number for your voice, or 0 if the list is full. 265 | * 266 | */ 267 | uint16_t(__cdecl* RegisterVoice)(const char* fileJP, const char* fileEN); 268 | 269 | // Replaces an individual texture from a GVM file with an image file. 270 | // Requires version >= 11. 271 | void(__cdecl* ReplaceTexture)(const char* gvm_name, const char* tex_name, const char* file_path, uint32_t gbix, uint32_t width, uint32_t height); 272 | 273 | // Removes any file replacements for the specified file. 274 | // Requires version >= 12. 275 | void(__cdecl* UnreplaceFile)(const char* file); 276 | /** 277 | * @brief Push Interpolation fix for animations. 278 | * 279 | * Use this at the beginning of a display function and please disable it at the end after so it doesn't run for all animations in the game. 280 | * Requires version >= 13. 281 | * 282 | */ 283 | void(__cdecl* PushInterpolationFix)(); 284 | 285 | // Disable interpolation fix for animations, use it at the end of a display function. 286 | // Requires version >= 13. 287 | void(__cdecl* PopInterpolationFix)(); 288 | }; 289 | 290 | typedef void(__cdecl* ModInitFunc)(const char* path, const HelperFunctions& helperFunctions); 291 | 292 | typedef void(__cdecl* ModEvent)(); 293 | 294 | struct ModInfo 295 | { 296 | int Version; 297 | void(__cdecl* Init)(const char* path, const HelperFunctions& helperFunctions); 298 | const PatchInfo* Patches; 299 | int PatchCount; 300 | const PointerInfo* Jumps; 301 | int JumpCount; 302 | const PointerInfo* Calls; 303 | int CallCount; 304 | const PointerInfo* Pointers; 305 | int PointerCount; 306 | }; 307 | 308 | #endif // SA2MODLOADER_SA2MODINFO_H -------------------------------------------------------------------------------- /SA2ModLoader/include/SA2ModLoader.h: -------------------------------------------------------------------------------- 1 | #ifndef SA2MODLOADER_H 2 | #define SA2MODLOADER_H 3 | 4 | #if !defined(_M_IX86) && !defined(__i386__) 5 | #error Mods must be built targeting 32-bit x86, change your settings. 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | #include "MemAccess.h" 12 | #include "SA2ModInfo.h" 13 | #include "SA2Enums.h" 14 | #include "SA2Structs.h" 15 | #include "SA2Variables.h" 16 | #include "SA2Functions.h" 17 | 18 | static inline void ResizeTextureList(NJS_TEXLIST* texlist, Uint32 count) 19 | { 20 | texlist->textures = new NJS_TEXNAME[count]{}; 21 | texlist->nbTexture = count; 22 | } 23 | 24 | static inline void ResizeTextureList(NJS_TEXLIST* texlist, NJS_TEXNAME* textures, Uint32 count) 25 | { 26 | texlist->textures = textures; 27 | texlist->nbTexture = count; 28 | } 29 | 30 | template 31 | static inline void ResizeTextureList(NJS_TEXLIST* texlist, NJS_TEXNAME(&textures)[N]) 32 | { 33 | ResizeTextureList(texlist, textures, N); 34 | } 35 | #endif -------------------------------------------------------------------------------- /SA2ModLoader/patches.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "SA2ModLoader.h" 3 | #include "UsercallFunctionHandler.h" 4 | #include "patches.h" 5 | #include "config.h" 6 | 7 | #pragma region Car Fix 8 | static const void* const loc_5E3D90 = (void*)0x5E3D90; 9 | static inline BOOL sub_5E3D90(LoopHead* path, int a2, float* a3, float a4, NJS_POINT3* out_pos) 10 | { 11 | int value; 12 | __asm 13 | { 14 | push[out_pos] 15 | push[a4] 16 | push[a3] 17 | push[a2] 18 | mov eax, [path] 19 | call loc_5E3D90 20 | mov value, eax 21 | add esp, 16 22 | } 23 | return value; 24 | } 25 | 26 | // Add additional safety checks when calling sub_5E3D90 to fix NaN car positions 27 | BOOL CalcCarPath_r(int a1, LoopHead* path, float* a3, float* a4, float a5, NJS_POINT3* out_pos, Angle3* out_ang) 28 | { 29 | NJS_POINT3 out1, out2; 30 | 31 | if (sub_5E3D90(path, a1, a3, a5, &out1) || 32 | sub_5E3D90(path, a1, a3, a4[15], &out1) || 33 | sub_5E3D90(path, a1, a3, a4[16], &out2)) { 34 | return TRUE; 35 | } 36 | 37 | NJS_POINT3 vec; 38 | vec.x = out1.x - out2.x; 39 | vec.y = out1.y - out2.y; 40 | vec.z = out1.z - out2.z; 41 | 42 | float dist = vec.x * vec.x + vec.z * vec.z; 43 | 44 | if (dist <= 0.0f) 45 | dist = 0.0f; 46 | 47 | out_ang->x = NJM_RAD_ANG(atan2f(-vec.y, sqrtf(dist))); 48 | out_ang->y = NJM_RAD_ANG(atan2f(vec.x, vec.z)); 49 | out_ang->z = 0; 50 | 51 | vec.x = vec.x * 0.5f; 52 | vec.y = vec.y * 0.5f; 53 | vec.z = vec.z * 0.5f; 54 | 55 | out_pos->x = out2.x + vec.x; 56 | out_pos->y = out2.y + vec.y; 57 | out_pos->z = out2.z + vec.z; 58 | 59 | return FALSE; 60 | } 61 | #pragma endregion 62 | 63 | #pragma region ScreenFade 64 | int ScreenFade_ri(int targetAlpha) 65 | { 66 | signed int increment = 6; // ecx 67 | 68 | if (*(char*)0x174AFCF != 2 || CurrentObjectSub) 69 | increment = 5; 70 | if (ScreenFadeARGB.argb.a == targetAlpha) 71 | return 1; 72 | else if (ScreenFadeARGB.argb.a > targetAlpha) 73 | { 74 | if (ScreenFadeARGB.argb.a - increment <= targetAlpha) 75 | ScreenFadeARGB.argb.a = targetAlpha; 76 | else 77 | ScreenFadeARGB.argb.a -= increment; 78 | } 79 | else // (ScreenFadeARGB.argb.a < targetAlpha) 80 | { 81 | if (ScreenFadeARGB.argb.a + increment >= targetAlpha) 82 | ScreenFadeARGB.argb.a = targetAlpha; 83 | else 84 | ScreenFadeARGB.argb.a += increment; 85 | } 86 | return 0; 87 | } 88 | 89 | static void __declspec(naked) ScreenFade_r() 90 | { 91 | __asm 92 | { 93 | push edx // targetAlpha 94 | 95 | // Call your __cdecl function here: 96 | call ScreenFade_ri 97 | 98 | pop edx // targetAlpha 99 | retn 100 | } 101 | } 102 | #pragma endregion 103 | 104 | #pragma region Particles 105 | struct PosUVColorVert { 106 | NJS_VECTOR Pos; 107 | Uint32 color; 108 | Float u, v; 109 | }; 110 | 111 | struct PosUVNormalColorVert { 112 | NJS_VECTOR Pos, Normal; 113 | Uint32 color; 114 | Float u, v; 115 | }; 116 | 117 | VoidFunc(GXEnd, 0x0041C070); 118 | 119 | DataPointer(int, GX_vtxcount, 0x01933F04); 120 | DataPointer(void*, VertexDeclarationInfoInstance, 0x0174F7E8); 121 | DataPointer(void*, PosUVColor, 0x0174F7FC); 122 | DataPointer(void*, PosNormalUVColor, 0x174F814); 123 | DataPointer(char*, VertexBuffer, 0x01933EF8); 124 | DataPointer(int, VertexBufferStart, 0x01933F08); 125 | DataPointer(int, VertexBufferOffset, 0x01933F0C); 126 | 127 | void ParticleGXEnd() 128 | { 129 | // If the vertex buffer does not contain normals 130 | if (VertexDeclarationInfoInstance == PosUVColor) 131 | { 132 | // Use vertex declaration with normals 133 | VertexDeclarationInfoInstance = PosNormalUVColor; 134 | 135 | // Remake the vertex buffer 136 | PosUVColorVert origvert[4]; 137 | PosUVNormalColorVert* newvert = (PosUVNormalColorVert*)(VertexBuffer + VertexBufferStart); 138 | 139 | memcpy(origvert, VertexBuffer + VertexBufferStart, sizeof(PosUVColorVert) * GX_vtxcount); 140 | 141 | for (int i = 0; i < GX_vtxcount; i++) { 142 | newvert[i].Pos = origvert[i].Pos; 143 | newvert[i].Normal = { 0.0f, 1.0f, 0.0f }; 144 | newvert[i].u = origvert[i].u; 145 | newvert[i].v = origvert[i].v; 146 | newvert[i].color = origvert[i].color; 147 | } 148 | 149 | // Set new vertex buffer offset 150 | VertexBufferOffset = VertexBufferStart + sizeof(PosNormalUVColor) * GX_vtxcount; 151 | 152 | // Send the new buffer 153 | GXEnd(); 154 | 155 | // Revert vertex declaration changes 156 | VertexDeclarationInfoInstance = PosUVColor; 157 | } 158 | else 159 | { 160 | GXEnd(); 161 | } 162 | } 163 | #pragma endregion 164 | 165 | void SyncLoad(void (*a1)(void*), void* a2) 166 | { 167 | a1(a2); 168 | } 169 | 170 | void ApplyPatches(LoaderSettings* loaderSettings) 171 | { 172 | // Expand chunk model vertex buffer from 8192 to 32768 verts 173 | if (loaderSettings->ExtendVertexBuffer || IsGamePatchEnabled("ExtendVertexBuffer")) 174 | *(void**)0x25EFE48 = calloc(1, 0x100004); 175 | 176 | // Fix env map condition bug in chDrawCnk 177 | if (loaderSettings->EnvMapFix || IsGamePatchEnabled("EnvMapFix")) 178 | WriteData<6>((char*)0x0056DE7D, (char)0x90); 179 | 180 | if (loaderSettings->SyncLoad || IsGamePatchEnabled("SyncLoad")) 181 | { 182 | WriteJump((void*)0x428470, SyncLoad); 183 | WriteJump((void*)0x428740, SyncLoad); 184 | } 185 | 186 | // Fix screen flickering during victory pose. 187 | if (loaderSettings->ScreenFadeFix || IsGamePatchEnabled("ScreenFadeFix")) 188 | WriteJump((void*)ScreenFadePtr, ScreenFade_r); 189 | 190 | // Fix City Escape car bug on intel iGPUs caused by NaN float position 191 | if (loaderSettings->CECarFix || IsGamePatchEnabled("CECarFix")) 192 | GenerateUsercallHook(CalcCarPath_r, rEAX, 0x5DE0B0, rECX, rEDX, rEAX, stack4, stack4, stack4, stack4); 193 | 194 | // Fix particle draw calls missing normals 195 | if (loaderSettings->ParticlesFix || IsGamePatchEnabled("ParticlesFix")) 196 | { 197 | WriteJump((void*)0x4915E9, ParticleGXEnd); 198 | WriteCall((void*)0x491B90, ParticleGXEnd); 199 | WriteCall((void*)0x4917EE, ParticleGXEnd); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /SA2ModLoader/patches.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void ApplyPatches(LoaderSettings* loaderSettings); 4 | -------------------------------------------------------------------------------- /SA2ModLoader/pvmx.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "pvmx.h" 10 | 11 | #pragma region convenience 12 | 13 | template 14 | auto& write_t(std::ofstream& file, const T& data) 15 | { 16 | return file.write(reinterpret_cast(&data), sizeof(T)); 17 | } 18 | 19 | template 20 | auto& read_t(std::ifstream& file, T& data) 21 | { 22 | return file.read(reinterpret_cast(&data), sizeof(T)); 23 | } 24 | 25 | void read_cstr(std::ifstream& file, std::string& out) 26 | { 27 | std::stringstream buffer; 28 | 29 | while (true) 30 | { 31 | auto c = '\0'; 32 | read_t(file, c); 33 | 34 | if (c == '\0') 35 | { 36 | break; 37 | } 38 | 39 | buffer.put(c); 40 | } 41 | 42 | out = buffer.str(); 43 | } 44 | 45 | #pragma endregion 46 | 47 | namespace pvmx 48 | { 49 | bool check_header(std::ifstream& file) 50 | { 51 | if (!file.is_open()) 52 | { 53 | return false; 54 | } 55 | 56 | int fourcc; 57 | read_t(file, fourcc); 58 | 59 | if (fourcc != PVMX_FOURCC) 60 | { 61 | return false; 62 | } 63 | 64 | uint8_t version; 65 | read_t(file, version); 66 | 67 | return version == PVMX_VERSION; 68 | } 69 | 70 | bool is_pvmx(std::ifstream& file) 71 | { 72 | if (!file.is_open()) 73 | { 74 | return false; 75 | } 76 | 77 | const auto pos = file.tellg(); 78 | const auto result = check_header(file); 79 | file.seekg(pos); 80 | 81 | return result; 82 | } 83 | 84 | bool is_pvmx(const char* path) 85 | { 86 | std::ifstream file(path, std::ios::binary | std::ios::in); 87 | return check_header(file); 88 | } 89 | 90 | bool is_pvmx(const std::string& path) 91 | { 92 | return is_pvmx(path.c_str()); 93 | } 94 | 95 | bool read_index(std::ifstream& file, std::vector& out) 96 | { 97 | const auto pos = file.tellg(); 98 | 99 | if (!check_header(file)) 100 | { 101 | file.seekg(pos); 102 | return false; 103 | } 104 | 105 | uint8_t type = 0; 106 | 107 | for (read_t(file, type); type != dictionary_field::none; read_t(file, type)) 108 | { 109 | DictionaryEntry entry = {}; 110 | 111 | while (type != dictionary_field::none) 112 | { 113 | switch (type) 114 | { 115 | case dictionary_field::none: 116 | break; 117 | 118 | case dictionary_field::global_index: 119 | read_t(file, entry.global_index); 120 | break; 121 | 122 | case dictionary_field::name: 123 | read_cstr(file, entry.name); 124 | break; 125 | 126 | case dictionary_field::dimensions: 127 | read_t(file, entry.width); 128 | read_t(file, entry.height); 129 | break; 130 | 131 | // Unknown field type. 132 | default: 133 | return false; 134 | } 135 | 136 | read_t(file, type); 137 | } 138 | 139 | read_t(file, entry.offset); 140 | read_t(file, entry.size); 141 | 142 | out.push_back(entry); 143 | } 144 | 145 | return true; 146 | } 147 | 148 | bool get_entry(std::ifstream& file, const DictionaryEntry& entry, std::vector& out) 149 | { 150 | if (!file.is_open()) 151 | { 152 | return false; 153 | } 154 | 155 | out.resize(static_cast(entry.size)); 156 | 157 | const auto pos = static_cast(file.tellg()); 158 | file.read(reinterpret_cast(out.data()), out.size()); 159 | 160 | const auto delta = static_cast(file.tellg()) - pos; 161 | 162 | if (delta != entry.size) 163 | { 164 | out.clear(); 165 | return false; 166 | } 167 | 168 | return true; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /SA2ModLoader/pvmx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "TextureReplacement.h" 7 | 8 | constexpr uint32_t PVMX_FOURCC = 'XMVP'; 9 | constexpr uint8_t PVMX_VERSION = 1; 10 | 11 | namespace pvmx 12 | { 13 | struct DictionaryEntry : TexPackEntry 14 | { 15 | uint64_t offset; 16 | uint64_t size; 17 | }; 18 | 19 | namespace dictionary_field 20 | { 21 | enum _ : uint8_t 22 | { 23 | none, 24 | /** 25 | * \brief 32-bit integer global index 26 | */ 27 | global_index, 28 | /** 29 | * \brief Null-terminated file name 30 | */ 31 | name, 32 | /** 33 | * \brief Two 32-bit integers defining width and height 34 | */ 35 | dimensions, 36 | }; 37 | 38 | static_assert(sizeof(none) == sizeof(uint8_t), "dictionary_field size mismatch"); 39 | } 40 | 41 | /** 42 | * \brief Checks the header of the provided file and restores the stream position. 43 | * \param file An open stream of the file to check. 44 | * \return \c true if the file is a PVMX archive. 45 | */ 46 | bool is_pvmx(std::ifstream& file); 47 | 48 | /** 49 | * \brief Checks the header of the provided file and restores the stream position. 50 | * \param path A valid path to the file to be checked. 51 | * \return \c true if the file is a PVMX archive. 52 | */ 53 | bool is_pvmx(const char* path); 54 | 55 | /** 56 | * \brief Checks the header of the provided file and restores the stream position. 57 | * \param path A valid path to the file to be checked. 58 | * \return \c true if the file is a PVMX archive. 59 | */ 60 | bool is_pvmx(const std::string& path); 61 | 62 | /** 63 | * \brief Same as \a is_pvmx, but does not restore the stream position. 64 | * \param file An open stream of the file to check. 65 | * \return \c true if the file is a PVMX archive. 66 | */ 67 | bool check_header(std::ifstream& file); 68 | 69 | /** 70 | * \brief Reads the texture pack index from the given PVMX archive. 71 | * \param file The file to read from. 72 | * \param out A vector to be populated with the texture pack index. 73 | * \return \c true on success. 74 | */ 75 | bool read_index(std::ifstream& file, std::vector& out); 76 | 77 | /** 78 | * \brief Reads a texture entry into the provided buffer. 79 | * \param file The file to read from. 80 | * \param entry The entry whose data is to be read. 81 | * \param out The buffer to store the data. 82 | * \return \c true on success. 83 | */ 84 | bool get_entry(std::ifstream& file, const DictionaryEntry& entry, std::vector& out); 85 | } 86 | -------------------------------------------------------------------------------- /SA2ModLoader/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // SA2ModLoader.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /SA2ModLoader/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 11 | // Windows Header Files: 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "ninja.h" 33 | #include "SA2ModLoader.h" 34 | #include "Events.h" 35 | #include "FileReplacement.h" 36 | #include "Trampoline.h" 37 | #include "FunctionHook.h" -------------------------------------------------------------------------------- /SA2ModLoader/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /SA2ModLoader/testspawn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void TestSpawnCheckArgs(const HelperFunctions& helperFunctions); 4 | 5 | extern bool testSpawnCutscene; 6 | extern bool testSpawnLvl; -------------------------------------------------------------------------------- /SA2ModLoader/window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "IniFile.hpp" 3 | 4 | void PatchWindow(const LoaderSettings& settings, std::wstring& borderimg); -------------------------------------------------------------------------------- /UpgradeTool/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /UpgradeTool/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace UpgradeTool 3 | { 4 | partial class MainForm 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Windows Form Designer generated code 25 | 26 | 27 | /// 28 | /// Required method for Designer support - do not modify 29 | /// the contents of this method with the code editor. 30 | /// 31 | private void InitializeComponent() 32 | { 33 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); 34 | this.label1 = new System.Windows.Forms.Label(); 35 | this.button1 = new System.Windows.Forms.Button(); 36 | this.button2 = new System.Windows.Forms.Button(); 37 | this.SuspendLayout(); 38 | // 39 | // label1 40 | // 41 | this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 42 | this.label1.Location = new System.Drawing.Point(13, 13); 43 | this.label1.Name = "label1"; 44 | this.label1.Size = new System.Drawing.Size(416, 115); 45 | this.label1.TabIndex = 0; 46 | this.label1.Text = resources.GetString("label1.Text"); 47 | // 48 | // button1 49 | // 50 | this.button1.Font = new System.Drawing.Font("Microsoft Sans Serif", 20F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 51 | this.button1.Location = new System.Drawing.Point(12, 131); 52 | this.button1.Name = "button1"; 53 | this.button1.Size = new System.Drawing.Size(417, 160); 54 | this.button1.TabIndex = 1; 55 | this.button1.Text = "Get the new SA Mod Manager"; 56 | this.button1.UseVisualStyleBackColor = true; 57 | this.button1.Click += new System.EventHandler(this.button1_Click); 58 | // 59 | // button2 60 | // 61 | this.button2.Font = new System.Drawing.Font("Microsoft Sans Serif", 20F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 62 | this.button2.Location = new System.Drawing.Point(12, 297); 63 | this.button2.Name = "button2"; 64 | this.button2.Size = new System.Drawing.Size(417, 160); 65 | this.button2.TabIndex = 2; 66 | this.button2.Text = "Keep the " + managerName; 67 | this.button2.UseVisualStyleBackColor = true; 68 | this.button2.Click += new System.EventHandler(this.button2_Click); 69 | // 70 | // MainForm 71 | // 72 | this.AcceptButton = this.button1; 73 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 74 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 75 | this.AutoSize = true; 76 | this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 77 | this.ClientSize = new System.Drawing.Size(447, 478); 78 | this.Controls.Add(this.button2); 79 | this.Controls.Add(this.button1); 80 | this.Controls.Add(this.label1); 81 | this.MaximizeBox = false; 82 | this.MinimizeBox = false; 83 | this.Name = "MainForm"; 84 | this.Padding = new System.Windows.Forms.Padding(0, 0, 3, 3); 85 | this.ShowIcon = false; 86 | this.Text = managerName + " Upgrade"; 87 | this.ResumeLayout(false); 88 | 89 | } 90 | 91 | #endregion 92 | 93 | private System.Windows.Forms.Label label1; 94 | private System.Windows.Forms.Button button1; 95 | private System.Windows.Forms.Button button2; 96 | } 97 | } 98 | 99 | -------------------------------------------------------------------------------- /UpgradeTool/MainForm.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using ModManagerCommon; 3 | using ModManagerCommon.Forms; 4 | using Newtonsoft.Json; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Net.Http; 11 | using System.Threading.Tasks; 12 | using System.Windows.Forms; 13 | 14 | 15 | namespace UpgradeTool 16 | { 17 | public partial class MainForm : Form 18 | { 19 | public MainForm() 20 | { 21 | InitializeComponent(); 22 | } 23 | 24 | public static readonly List oneclickInstallList = new List 25 | { 26 | "sadxmm", 27 | "sa2mm", 28 | "sonicrmm" 29 | }; 30 | 31 | string updatePath = "mods/.updates"; 32 | public string datadllorigpath = @"resource\gd_PC\DLL\Win32\Data_DLL_orig.dll"; 33 | public string datadllpath = @"resource\gd_PC\DLL\Win32\Data_DLL.dll"; 34 | public const string managerName = "SA2 Mod Manager"; 35 | 36 | private void UninstallOldModLoader(string gameDirectory) 37 | { 38 | if (string.IsNullOrEmpty(gameDirectory) == false) 39 | { 40 | datadllpath = Path.GetFullPath(Path.Combine(gameDirectory, datadllpath)); 41 | datadllorigpath = Path.GetFullPath(Path.Combine(gameDirectory, datadllorigpath)); 42 | } 43 | 44 | if (File.Exists(datadllorigpath)) //remove the mod loader since we will use a new one. 45 | { 46 | File.Delete(datadllpath); 47 | File.Move(datadllorigpath, datadllpath); 48 | } 49 | } 50 | 51 | public static async Task GetLatestReleaseNewManager(HttpClient httpClient) 52 | { 53 | try 54 | { 55 | 56 | httpClient.DefaultRequestHeaders.Add("User-Agent", Program.oldLoaderExeName); 57 | 58 | HttpResponseMessage response = await httpClient.GetAsync("https://api.github.com/repos/X-Hax/SA-Mod-manager/releases/latest"); 59 | 60 | if (response.IsSuccessStatusCode) 61 | { 62 | string responseBody = await response.Content.ReadAsStringAsync(); 63 | var release = JsonConvert.DeserializeObject(responseBody); 64 | if (release != null && release.Assets != null) 65 | { 66 | var targetAsset = release.Assets.FirstOrDefault(asset => asset.Name.Contains(Environment.Is64BitOperatingSystem ? "x64" : "x86")); 67 | 68 | if (targetAsset != null) 69 | { 70 | return targetAsset.DownloadUrl; 71 | } 72 | } 73 | } 74 | } 75 | catch (Exception ex) 76 | { 77 | Console.WriteLine("Error fetching latest release: " + ex.Message); 78 | } 79 | 80 | return null; 81 | } 82 | 83 | private static string CleanPath(string path) 84 | { 85 | int exeIndex = path.IndexOf(".exe", StringComparison.OrdinalIgnoreCase); 86 | if (exeIndex != -1) 87 | { 88 | return path.Substring(0, exeIndex + 4); 89 | } 90 | else 91 | { 92 | return path; 93 | } 94 | } 95 | 96 | static string GetNewManagerPath() 97 | { 98 | try 99 | { 100 | foreach (var game in oneclickInstallList) 101 | { 102 | using (var hkcu = Registry.CurrentUser) 103 | using (var key = hkcu.OpenSubKey(@"Software\Classes\" + game + @"\shell\open\command")) 104 | { 105 | if (key != null) 106 | { 107 | string value = (string)key.GetValue(null); 108 | if (!string.IsNullOrEmpty(value)) 109 | { 110 | // Trim leading and trailing double quotes 111 | value = value.Trim('"'); 112 | return value; 113 | } 114 | } 115 | } 116 | } 117 | } 118 | catch { } 119 | 120 | return null; 121 | } 122 | 123 | private async void button1_Click(object sender, EventArgs e) 124 | { 125 | try 126 | { 127 | var NewManagerPath = CleanPath(GetNewManagerPath()); 128 | string gameDirectory = Program.FindGameDirectory(AppDomain.CurrentDomain.BaseDirectory, Program.exeName); 129 | 130 | if (File.Exists(NewManagerPath) && string.IsNullOrEmpty(gameDirectory) == false) 131 | { 132 | var msg = MessageBox.Show("It looks like you already have the new SA Mod Manager installed." + 133 | "\n\nDo you want to cleanup the old files and finish the migration? (Recommended).", "SA Mod Manager found", MessageBoxButtons.YesNo); 134 | 135 | if (msg == DialogResult.Yes) 136 | { 137 | NewManagerPath = Path.GetFullPath(NewManagerPath); //cleanup 138 | var startInfo = new ProcessStartInfo 139 | { 140 | FileName = NewManagerPath, 141 | Arguments = $"clearLegacy \"{gameDirectory}\"", 142 | UseShellExecute = true, 143 | WorkingDirectory = Path.GetDirectoryName(NewManagerPath), 144 | }; 145 | Process.Start(startInfo); 146 | Close(); 147 | return; 148 | } 149 | } 150 | 151 | var wc = new HttpClient(); 152 | var release = await GetLatestReleaseNewManager(wc) ?? (Environment.Is64BitOperatingSystem ? "https://github.com/X-Hax/SA-Mod-Manager/releases/latest/download/release_x64.zip" : "https://github.com/X-Hax/SA-Mod-Manager/releases/latest/download/release_x86.zip"); 153 | DialogResult result = DialogResult.OK; 154 | do 155 | { 156 | try 157 | { 158 | if (!Directory.Exists(updatePath)) 159 | { 160 | Directory.CreateDirectory(updatePath); 161 | } 162 | } 163 | catch (Exception ex) 164 | { 165 | result = MessageBox.Show(this, "Failed to create temporary update directory:\n" + ex.Message 166 | + "\n\nWould you like to retry?", "Directory Creation Failed", MessageBoxButtons.RetryCancel); 167 | if (result == DialogResult.Cancel) 168 | return; 169 | } 170 | } while (result == DialogResult.Retry); 171 | 172 | 173 | using (var dlg2 = new WPFDownloadDialog(release, updatePath)) 174 | if (dlg2.ShowDialog(this) == DialogResult.OK) 175 | { 176 | UninstallOldModLoader(gameDirectory); 177 | Close(); 178 | } 179 | 180 | } 181 | catch 182 | { 183 | MessageBox.Show(this, "Unable to retrieve update information.", managerName); 184 | } 185 | } 186 | 187 | private void button2_Click(object sender, EventArgs e) 188 | { 189 | DialogResult result = DialogResult.OK; 190 | do 191 | { 192 | try 193 | { 194 | if (!Directory.Exists(updatePath)) 195 | { 196 | Directory.CreateDirectory(updatePath); 197 | } 198 | } 199 | catch (Exception ex) 200 | { 201 | result = MessageBox.Show(this, "Failed to create temporary update directory:\n" + ex.Message 202 | + "\n\nWould you like to retry?", "Directory Creation Failed", MessageBoxButtons.RetryCancel); 203 | 204 | if (result == DialogResult.Cancel) 205 | return; 206 | } 207 | } while (result == DialogResult.Retry); 208 | 209 | using (var dlg2 = new LoaderDownloadDialog("http://mm.reimuhakurei.net/sa2mods/SA2ModLoaderLegacy.7z", updatePath)) 210 | if (dlg2.ShowDialog(this) == DialogResult.OK) 211 | { 212 | Close(); 213 | } 214 | 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /UpgradeTool/MainForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | The SA2 Mod Manager is being deprecated, and will not be supported in the future. You can upgrade to the new SA Mod Manager (requires .NET 7) to keep up with the latest features, or you can install the legacy version of the SA2 Mod Manager, which will not receive any updates. 122 | 123 | -------------------------------------------------------------------------------- /UpgradeTool/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Windows.Forms; 5 | 6 | namespace UpgradeTool 7 | { 8 | static class Program 9 | { 10 | private const string pipeName = "sa2-mod-manager"; 11 | private static readonly Mutex mutex = new Mutex(true, pipeName); 12 | public const string exeName = "sonic2app.exe"; 13 | public const string oldLoaderExeName = "SA2ModLoader"; 14 | 15 | /// 16 | /// The main entry point for the application. 17 | /// 18 | [STAThread] 19 | static void Main(string[] args) 20 | { 21 | try { mutex.WaitOne(); } 22 | catch (AbandonedMutexException) { } 23 | 24 | if (args.Length > 1 && args[0] == "doupdate") 25 | File.Delete(args[1] + ".7z"); 26 | Application.EnableVisualStyles(); 27 | Application.SetCompatibleTextRenderingDefault(false); 28 | Application.Run(new MainForm()); 29 | } 30 | 31 | public static string FindGameDirectory(string filePath, string exeName) 32 | { 33 | string directory = Path.GetDirectoryName(filePath); 34 | while (directory != null) 35 | { 36 | if (Directory.GetFiles(directory, exeName).Length > 0) 37 | { 38 | return directory; 39 | } 40 | directory = Directory.GetParent(directory)?.FullName; 41 | } 42 | return null; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /UpgradeTool/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("UpgradeTool")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("UpgradeTool")] 13 | [assembly: AssemblyCopyright("Copyright © 2024")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("ba2f1c16-4790-4663-80fb-1c87ea8a021d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /UpgradeTool/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace UpgradeTool.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UpgradeTool.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /UpgradeTool/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /UpgradeTool/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace UpgradeTool.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /UpgradeTool/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /UpgradeTool/UpgradeTool.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BA2F1C16-4790-4663-80FB-1C87EA8A021D} 8 | WinExe 9 | UpgradeTool 10 | UpgradeTool 11 | v4.8 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | ..\bin\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | ..\bin\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | False 38 | ..\mod-loader-common\ModManagerCommon\Newtonsoft.Json.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | Form 55 | 56 | 57 | MainForm.cs 58 | 59 | 60 | 61 | 62 | Form 63 | 64 | 65 | MainForm.cs 66 | Designer 67 | 68 | 69 | ResXFileCodeGenerator 70 | Resources.Designer.cs 71 | Designer 72 | 73 | 74 | True 75 | Resources.resx 76 | 77 | 78 | SettingsSingleFileGenerator 79 | Settings.Designer.cs 80 | 81 | 82 | True 83 | Settings.settings 84 | True 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | {4a480502-79b5-4e1e-8d67-16c514bb13cd} 93 | ModManagerCommon 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /UpgradeTool/WPFDownloadDialog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Reflection; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using System.Windows.Forms; 11 | using UpgradeTool; 12 | 13 | namespace ModManagerCommon.Forms 14 | { 15 | public class WPFDownloadDialog : ProgressDialog 16 | { 17 | private readonly string url; 18 | private readonly string updatePath; 19 | private readonly CancellationTokenSource tokenSource = new CancellationTokenSource(); 20 | 21 | public WPFDownloadDialog(string url, string updatePath) 22 | : base("Download Progress", true) 23 | { 24 | this.url = url; 25 | this.updatePath = Path.GetFullPath(updatePath); 26 | 27 | Shown += OnShown; 28 | CancelEvent += OnCancelEvent; 29 | } 30 | 31 | private void OnCancelEvent(object sender, EventArgs eventArgs) 32 | { 33 | tokenSource.Cancel(); 34 | } 35 | 36 | private void OnShown(object sender, EventArgs eventArgs) 37 | { 38 | DialogResult = DialogResult.OK; 39 | 40 | SetTaskCount(1); 41 | 42 | using (var client = new UpdaterWebClient()) 43 | { 44 | CancellationToken token = tokenSource.Token; 45 | 46 | void OnDownloadProgress(object o, DownloadProgressEventArgs args) 47 | { 48 | SetProgress(args.BytesReceived / (double)args.TotalBytesToReceive); 49 | SetTaskAndStep($"Downloading file:", 50 | $"({SizeSuffix.GetSizeSuffix(args.BytesReceived)} / {SizeSuffix.GetSizeSuffix(args.TotalBytesToReceive)})"); 51 | args.Cancel = token.IsCancellationRequested; 52 | } 53 | void OnDownloadCompleted(object o, CancelEventArgs args) 54 | { 55 | NextTask(); 56 | args.Cancel = token.IsCancellationRequested; 57 | } 58 | 59 | DialogResult result; 60 | 61 | SetTaskAndStep("Starting download..."); 62 | 63 | do 64 | { 65 | result = DialogResult.Cancel; 66 | 67 | try 68 | { 69 | // poor man's await Task.Run (not available in .net 4.0) 70 | using (var task = new Task(() => 71 | { 72 | var cancelArgs = new CancelEventArgs(false); 73 | DownloadProgressEventArgs downloadArgs = null; 74 | 75 | void DownloadComplete(object _sender, AsyncCompletedEventArgs args) 76 | { 77 | lock (args.UserState) 78 | { 79 | Monitor.Pulse(args.UserState); 80 | } 81 | } 82 | 83 | void DownloadProgressChanged(object _sender, DownloadProgressChangedEventArgs args) 84 | { 85 | downloadArgs = new DownloadProgressEventArgs(args, 1, 1); 86 | OnDownloadProgress(this, downloadArgs); 87 | if (downloadArgs.Cancel) 88 | { 89 | client.CancelAsync(); 90 | } 91 | } 92 | 93 | var uri = new Uri(url); 94 | string filePath = Path.GetFullPath(Path.Combine(updatePath, uri.Segments.Last())); 95 | 96 | var info = new FileInfo(filePath); 97 | client.DownloadFileCompleted += DownloadComplete; 98 | client.DownloadProgressChanged += DownloadProgressChanged; 99 | 100 | var sync = new object(); 101 | lock (sync) 102 | { 103 | client.DownloadFileAsync(uri, filePath, sync); 104 | Monitor.Wait(sync); 105 | } 106 | 107 | client.DownloadProgressChanged -= DownloadProgressChanged; 108 | client.DownloadFileCompleted -= DownloadComplete; 109 | 110 | if (cancelArgs.Cancel || downloadArgs?.Cancel == true) 111 | { 112 | return; 113 | } 114 | 115 | OnDownloadCompleted(this, cancelArgs); 116 | if (cancelArgs.Cancel) 117 | { 118 | return; 119 | } 120 | 121 | string managerDLPath = updatePath; 122 | 123 | SetTaskAndStep("Extracting..."); 124 | if (token.IsCancellationRequested) 125 | { 126 | return; 127 | } 128 | 129 | Process.Start(new ProcessStartInfo("7z.exe", $"x -aoa -o\"{managerDLPath}\" \"{filePath}\"") { UseShellExecute = false, CreateNoWindow = true }).WaitForExit(); 130 | string NewManagerPath = Path.GetFullPath(Path.Combine(managerDLPath, "SAModManager.exe")); 131 | string dest = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SAModManager.exe"); 132 | 133 | string gameDirectory = Program.FindGameDirectory(AppDomain.CurrentDomain.BaseDirectory, Program.exeName); 134 | 135 | if (gameDirectory is null) //dirty failsafe but in theory shouldn't be needed 136 | { 137 | bool first = dest.ToLower().Contains("mods/.updates"); 138 | bool second = dest.ToLower().Contains("mods\\.updates"); 139 | 140 | if (first) 141 | { 142 | dest = dest.Replace("mods/.updates/" + Program.oldLoaderExeName, ""); 143 | } 144 | else if (second) 145 | { 146 | dest = dest.Replace("mods\\.updates\\" + Program.oldLoaderExeName, ""); 147 | } 148 | } 149 | else 150 | { 151 | dest = Path.Combine(gameDirectory, "SAModManager.exe"); 152 | } 153 | 154 | if (File.Exists(NewManagerPath)) 155 | { 156 | if (File.Exists(dest)) //just in case 157 | { 158 | File.Delete(dest); 159 | } 160 | 161 | File.Move(NewManagerPath, dest); //we move the new manager to the game directory 162 | } 163 | 164 | if (File.Exists("SAModManager.exe")) 165 | { 166 | var startInfo = new ProcessStartInfo 167 | { 168 | FileName = "SAModManager.exe", 169 | Arguments = "vanillaUpdate", 170 | UseShellExecute = true, 171 | WorkingDirectory = Environment.CurrentDirectory, 172 | }; 173 | Process.Start(startInfo); 174 | } 175 | else if (File.Exists(dest)) 176 | { 177 | var startInfo = new ProcessStartInfo 178 | { 179 | FileName = dest, 180 | Arguments = "vanillaUpdate", 181 | UseShellExecute = true, 182 | WorkingDirectory = Path.GetDirectoryName(dest), 183 | }; 184 | Process.Start(startInfo); 185 | } 186 | else 187 | { 188 | MessageBox.Show("Failed to open the new manager\nplease download the update manually."); 189 | } 190 | }, token)) 191 | { 192 | task.Start(); 193 | 194 | while (!task.IsCompleted && !task.IsCanceled) 195 | { 196 | Application.DoEvents(); 197 | } 198 | 199 | task.Wait(token); 200 | } 201 | } 202 | catch (AggregateException ae) 203 | { 204 | ae.Handle(ex => 205 | { 206 | result = MessageBox.Show(this, $"Failed to update:\r\n{ex.Message}", 207 | "Update Failed", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error); 208 | return true; 209 | }); 210 | } 211 | } while (result == DialogResult.Retry); 212 | 213 | } 214 | } 215 | 216 | protected override void Dispose(bool disposing) 217 | { 218 | tokenSource.Dispose(); 219 | base.Dispose(disposing); 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | pull_requests: 3 | do_not_increment_build_number: true 4 | branches: 5 | only: 6 | - master 7 | skip_tags: true 8 | image: Visual Studio 2019 9 | configuration: Release 10 | platform: x86 11 | before_build: 12 | - cmd: git submodule update --init --recursive 13 | build: 14 | project: SA2ModLoader.sln 15 | verbosity: minimal 16 | after_build: 17 | - cmd: >- 18 | mkdir C:\sa2mm 19 | 20 | mkdir C:\sa2mm\extlib 21 | 22 | mkdir C:\sa2mm\extlib\BASS 23 | 24 | copy C:\projects\sa2-mod-loader\bin\SA2ModLoader.dll C:\sa2mm\SA2ModLoader.dll 25 | 26 | copy C:\projects\sa2-mod-loader\data\*.* C:\sa2mm\ 27 | 28 | copy C:\projects\sa2-mod-loader\extlib\bass\*.dll C:\sa2mm\extlib\BASS\ 29 | 30 | echo | set /p dummyName="%APPVEYOR_BUILD_VERSION%" > "C:\sa2mm\sa2mlver.txt" 31 | 32 | 7z a C:\projects\sa2-mod-loader\bin\SA2ModLoader.7z C:\sa2mm\* 33 | artifacts: 34 | - path: bin\SA2ModLoader.7z 35 | 36 | - path: bin\SA2ModLoader.pdb 37 | 38 | before_deploy: 39 | - ps: |- 40 | if (!$env:APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED) { 41 | $env:APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED = "$env:APPVEYOR_REPO_COMMIT_MESSAGE" 42 | } 43 | 44 | deploy: 45 | - provider: GitHub 46 | description: $(APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED) 47 | auth_token: 48 | secure: 3U0hYOvkeEJsoN2b4U/c9RTDxjcj9txunvCbWfkg5qvOSB1qOnh4fnIE9EK5yAXi2m8RJMO4Zr/UqiVMB9CDPulfPRcJSJ83rsmh1EXBipUs4K/RQmQHaDG6UiiNQ1Xg 49 | artifact: bin/SA2ModLoader.7z,bin/SA2ModLoader.pdb 50 | on: 51 | branch: master -------------------------------------------------------------------------------- /data/Border_Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/data/Border_Default.png -------------------------------------------------------------------------------- /data/DebugFontTexture.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/data/DebugFontTexture.dds -------------------------------------------------------------------------------- /data/DebugTextShader.hlsl: -------------------------------------------------------------------------------- 1 | struct VS_IN 2 | { 3 | float3 position : POSITION; 4 | float4 color : COLOR0; 5 | float2 tex : TEXCOORD0; 6 | }; 7 | 8 | struct PS_IN 9 | { 10 | float4 position : POSITION; 11 | float4 color : COLOR; 12 | float2 uv : TEXCOORD; 13 | }; 14 | 15 | float4 gDeviceInfo : register(c104); 16 | 17 | PS_IN main(VS_IN input) 18 | { 19 | PS_IN output; 20 | float2 r0; 21 | float resX = gDeviceInfo.x / 2; 22 | float resY = gDeviceInfo.y / 2; 23 | r0.x = -input.position.x - 0.5f; 24 | r0.y = -input.position.y - 0.5f; 25 | r0.x = (r0.x / resX) - 1; 26 | r0.y = (r0.y / resY) - 1; 27 | output.position.x = r0.x; 28 | output.position.y = -r0.y; 29 | output.position.z = (1 + input.position.z) * r0.x; 30 | output.position.w = 1; 31 | output.color = input.color; 32 | output.uv = input.tex; 33 | return output; 34 | } -------------------------------------------------------------------------------- /data/Patches.json: -------------------------------------------------------------------------------- 1 | { 2 | "Patches": [ 3 | { 4 | "Name": "FramerateLimiter", 5 | "Author": "SF94 and Kell", 6 | "Category": "Graphics", 7 | "InternalName": "Limit Framerate", 8 | "Description": "Limits the framerate to 60fps. This prevents the game from running double speed or faster when a monitor has a higher refresh rate than 60hz.", 9 | "IsChecked": true 10 | }, 11 | { 12 | "Name": "DisableExitPrompt", 13 | "Author": "Kell", 14 | "Category": "System", 15 | "InternalName": "Disable Exit Prompt", 16 | "Description": "Disables the game's exit prompt from displaying when closing the game window.", 17 | "IsChecked": true 18 | }, 19 | { 20 | "Name": "SyncLoad", 21 | "Author": "MainMemory", 22 | "Category": "System", 23 | "InternalName": "Synchronous File Loading", 24 | "Description": "Loads files synchronously, speeding it up. It may improve stability on some systems.", 25 | "IsChecked": true 26 | }, 27 | { 28 | "Name": "ExtendVertexBuffer", 29 | "Author": "MainMemory", 30 | "Category": "System", 31 | "InternalName": "Extend Vertex Buffer for Models", 32 | "Description": "Extends the vertex buffer for models to allow the use of higher vertex counts on models. Some mods may rely on this being enabled.", 33 | "IsChecked": true 34 | }, 35 | { 36 | "Name": "EnvMapFix", 37 | "Author": "Exant", 38 | "Category": "Graphics", 39 | "InternalName": "Fix Environment Mapping", 40 | "Description": "Fixes environment map rendering.", 41 | "IsChecked": true 42 | }, 43 | { 44 | "Name": "ScreenFadeFix", 45 | "Author": "MainMemory", 46 | "Category": "Graphics", 47 | "InternalName": "Fix Screen Fade", 48 | "Description": "Adjusts the Screen Fade on the result loading screen.", 49 | "IsChecked": true 50 | }, 51 | { 52 | "Name": "CECarFix", 53 | "Author": "Kell", 54 | "Category": "Graphics", 55 | "InternalName": "City Escape Car Fixes", 56 | "Description": "Fixes an issue where the Trolley will sometimes appear over the player's head on some graphics chipsets/gpus, and where vehicles may not load correctly either.", 57 | "IsChecked": true 58 | }, 59 | { 60 | "Name": "ParticlesFix", 61 | "Author": "Kell", 62 | "Category": "Graphics", 63 | "InternalName": "Particle Rendering Fixes", 64 | "Description": "Fixes particles failing to render on certain graphics chipsets/gpus.", 65 | "IsChecked": true 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /extlib/SA2ModLoader.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/extlib/SA2ModLoader.dll -------------------------------------------------------------------------------- /extlib/bass/COPYING_BASS_VGMSTREAM: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, angryzor 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /extlib/bass/COPYING_VGMSTREAM: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2009 Adam Gashlin, Fastelbja, Ronny Elfert 2 | 3 | Portions Copyright (c) 2004-2008, Marko Kreen 4 | Portions Copyright 2001-2007 jagarl / Kazunori Ueno 5 | Portions Copyright (c) 1998, Justin Frankel/Nullsoft Inc. 6 | Portions Copyright (C) 2006 Nullsoft, Inc. 7 | Portions Copyright (c) 2005-2007 Paul Hsieh 8 | Portions Public Domain originating with Sun Microsystems 9 | 10 | Permission to use, copy, modify, and distribute this software for any 11 | purpose with or without fee is hereby granted, provided that the above 12 | copyright notice and this permission notice appear in all copies. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 15 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 17 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 18 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 19 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 20 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 21 | -------------------------------------------------------------------------------- /extlib/bass/avcodec-vgmstream-59.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/extlib/bass/avcodec-vgmstream-59.dll -------------------------------------------------------------------------------- /extlib/bass/avformat-vgmstream-59.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/extlib/bass/avformat-vgmstream-59.dll -------------------------------------------------------------------------------- /extlib/bass/avutil-vgmstream-57.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/extlib/bass/avutil-vgmstream-57.dll -------------------------------------------------------------------------------- /extlib/bass/bass.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/extlib/bass/bass.dll -------------------------------------------------------------------------------- /extlib/bass/bass.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/extlib/bass/bass.lib -------------------------------------------------------------------------------- /extlib/bass/bass_vgmstream.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/extlib/bass/bass_vgmstream.dll -------------------------------------------------------------------------------- /extlib/bass/bass_vgmstream.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2008, Ruben "angryzor" Tytgat 3 | * 4 | * Permission to use, copy, modify, and/or distribute this 5 | * software for any purpose with or without fee is hereby granted, 6 | * provided that the above copyright notice and this permission 7 | * notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS 10 | * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 11 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. 12 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, 13 | * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 15 | * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 16 | * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH 17 | * THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | #ifndef _BASS_VGMSTREAM_H_ 21 | #define _BASS_VGMSTREAM_H_ 22 | 23 | #ifdef BASS_VGMSTREAM_EXPORTS 24 | #define BASS_VGMSTREAM_API __declspec(dllexport) 25 | #else 26 | #define BASS_VGMSTREAM_API __declspec(dllimport) 27 | #endif 28 | 29 | #include 30 | 31 | 32 | #ifdef __cplusplus 33 | extern "C" 34 | { 35 | #endif 36 | 37 | BASS_VGMSTREAM_API HSTREAM BASS_VGMSTREAM_StreamCreate(const char* file, DWORD flags); 38 | BASS_VGMSTREAM_API HSTREAM BASS_VGMSTREAM_StreamCreateFromMemory(unsigned char* buf, int bufsize, const char* name, DWORD flags); 39 | BASS_VGMSTREAM_API void* BASS_VGMSTREAM_InitVGMStreamFromMemory(void* data, int size, const char* name); 40 | BASS_VGMSTREAM_API void BASS_VGMSTREAM_CloseVGMStream(void* vgmstream); 41 | BASS_VGMSTREAM_API int BASS_VGMSTREAM_GetVGMStreamOutputSize(void* vgmstream); 42 | BASS_VGMSTREAM_API int BASS_VGMSTREAM_ConvertVGMStreamToWav(void* vgmstream, unsigned char* outputdata); 43 | 44 | #ifdef __cplusplus 45 | } 46 | #endif 47 | 48 | 49 | #endif -------------------------------------------------------------------------------- /extlib/bass/bass_vgmstream.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/extlib/bass/bass_vgmstream.lib -------------------------------------------------------------------------------- /extlib/bass/libmpg123-0.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/extlib/bass/libmpg123-0.dll -------------------------------------------------------------------------------- /extlib/bass/libvorbis.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/extlib/bass/libvorbis.dll -------------------------------------------------------------------------------- /extlib/bass/swresample-vgmstream-4.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Hax/sa2-mod-loader/be64c3fd576fd3f2cf41cbb9818d7bd52722ddef/extlib/bass/swresample-vgmstream-4.dll -------------------------------------------------------------------------------- /libmodutils/AnimationFile.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "AnimationFile.h" 3 | #include 4 | #include 5 | using std::default_delete; 6 | using std::ifstream; 7 | using std::ios; 8 | using std::istream; 9 | using std::list; 10 | using std::shared_ptr; 11 | using std::streamoff; 12 | using std::string; 13 | #ifdef _MSC_VER 14 | using std::wstring; 15 | #endif /* _MSC_VER */ 16 | 17 | AnimationFile::AnimationFile(const char* filename) 18 | { 19 | ifstream str(filename, ios::binary); 20 | init(str); 21 | str.close(); 22 | } 23 | 24 | #ifdef _MSC_VER 25 | AnimationFile::AnimationFile(const wchar_t* filename) 26 | { 27 | ifstream str(filename, ios::binary); 28 | init(str); 29 | str.close(); 30 | } 31 | #endif /* _MSC_VER */ 32 | 33 | AnimationFile::AnimationFile(const string& filename) 34 | { 35 | ifstream str(filename, ios::binary); 36 | init(str); 37 | str.close(); 38 | } 39 | 40 | #ifdef _MSC_VER 41 | AnimationFile::AnimationFile(const wstring& filename) 42 | { 43 | ifstream str(filename, ios::binary); 44 | init(str); 45 | str.close(); 46 | } 47 | #endif /* _MSC_VER */ 48 | 49 | AnimationFile::AnimationFile(istream& stream) { init(stream); } 50 | 51 | NJS_MOTION* AnimationFile::getmotion() const { return motion; } 52 | 53 | int AnimationFile::getmodelcount() const { return modelcount; } 54 | 55 | bool AnimationFile::isshortrot() const { return shortrot; } 56 | 57 | const string& AnimationFile::getlabel() 58 | { 59 | return getlabel(motion); 60 | } 61 | 62 | static const string empty; 63 | const string& AnimationFile::getlabel(void* data) 64 | { 65 | auto elem = labels1.find(data); 66 | if (elem == labels1.end()) 67 | return empty; 68 | else 69 | return elem->second; 70 | } 71 | 72 | void* AnimationFile::getdata(const string& label) 73 | { 74 | auto elem = labels2.find(label); 75 | if (elem == labels2.end()) 76 | return nullptr; 77 | else 78 | return elem->second; 79 | } 80 | 81 | const std::unordered_map* AnimationFile::getlabels() const 82 | { 83 | return &labels2; 84 | } 85 | 86 | static string getstring(istream& stream) 87 | { 88 | auto start = stream.tellg(); 89 | while (stream.get() != 0) {} 90 | const auto size = static_cast(stream.tellg() - start); 91 | char* buf = new char[size]; 92 | stream.seekg(start); 93 | stream.read(buf, size); 94 | string result(buf); 95 | delete[] buf; 96 | return result; 97 | } 98 | 99 | template 100 | static inline void fixptr(T*& ptr, intptr_t base) 101 | { 102 | if (ptr != nullptr) 103 | { 104 | ptr = (T*)((uint8_t*)ptr + base); 105 | } 106 | } 107 | 108 | inline void fixmkeypointers(NJS_MKEY_P* mkey, intptr_t base, int count) 109 | { 110 | for (int i = 0; i < count; i++) 111 | fixptr(mkey[i].key, base); 112 | } 113 | 114 | template 115 | inline void fixmdatapointers(T* mdata, intptr_t base, int count, int vertoff, int normoff) 116 | { 117 | for (int c = 0; c < count; c++) 118 | { 119 | for (int i = 0; i < (int)LengthOfArray(mdata->p); i++) 120 | { 121 | fixptr(mdata->p[i], base); 122 | if (i == vertoff || i == normoff) 123 | fixmkeypointers((NJS_MKEY_P*)mdata->p[i], base, mdata->nb[i]); 124 | } 125 | 126 | ++mdata; 127 | } 128 | } 129 | 130 | template 131 | static inline void readdata(istream& stream, T& data) 132 | { 133 | stream.read((char*)&data, sizeof(T)); 134 | } 135 | 136 | void AnimationFile::init(istream& stream) 137 | { 138 | uint64_t magic; 139 | readdata(stream, magic); 140 | uint8_t version = magic >> 56; 141 | magic &= FormatMask; 142 | 143 | if (version > CurrentVersion) // unrecognized file version 144 | { 145 | return; 146 | } 147 | 148 | if (magic != SAANIM) 149 | { 150 | return; 151 | } 152 | 153 | uint32_t landtableoff; 154 | readdata(stream, landtableoff); 155 | landtableoff -= headersize; 156 | 157 | uint32_t tmpaddr; 158 | readdata(stream, tmpaddr); 159 | readdata(stream, modelcount); 160 | 161 | shortrot = modelcount < 0; 162 | modelcount &= INT32_MAX; 163 | 164 | size_t mdlsize = tmpaddr - headersize; 165 | auto motionbuf = new uint8_t[mdlsize]; 166 | 167 | allocatedmem.push_back(shared_ptr(motionbuf, default_delete())); 168 | 169 | stream.read((char*)motionbuf, mdlsize); 170 | motion = (NJS_MOTION*)(motionbuf + landtableoff); 171 | 172 | intptr_t motionbase = (intptr_t)motionbuf - headersize; 173 | 174 | if (motion->mdata != nullptr) 175 | { 176 | motion->mdata = (uint8_t*)motion->mdata + motionbase; 177 | 178 | if (fixedpointers.find(motion->mdata) == fixedpointers.end()) 179 | { 180 | fixedpointers.insert(motion->mdata); 181 | 182 | int vertoff = -1; 183 | int normoff = -1; 184 | 185 | if (motion->type & NJD_MTYPE_VERT_4) 186 | { 187 | vertoff = 0; 188 | if (motion->type & NJD_MTYPE_POS_0) 189 | ++vertoff; 190 | if (motion->type & NJD_MTYPE_ANG_1) 191 | ++vertoff; 192 | if (motion->type & NJD_MTYPE_QUAT_1) 193 | ++vertoff; 194 | if (motion->type & NJD_MTYPE_SCL_2) 195 | ++vertoff; 196 | if (motion->type & NJD_MTYPE_TARGET_3) 197 | ++vertoff; 198 | if (motion->type & NJD_MTYPE_VEC_3) 199 | ++vertoff; 200 | } 201 | if (motion->type & NJD_MTYPE_NORM_5) 202 | { 203 | normoff = 0; 204 | if (motion->type & NJD_MTYPE_POS_0) 205 | ++normoff; 206 | if (motion->type & NJD_MTYPE_ANG_1) 207 | ++normoff; 208 | if (motion->type & NJD_MTYPE_QUAT_1) 209 | ++normoff; 210 | if (motion->type & NJD_MTYPE_SCL_2) 211 | ++normoff; 212 | if (motion->type & NJD_MTYPE_TARGET_3) 213 | ++normoff; 214 | if (motion->type & NJD_MTYPE_VEC_3) 215 | ++normoff; 216 | if (motion->type & NJD_MTYPE_VERT_4) 217 | ++normoff; 218 | } 219 | switch (motion->inp_fn & 0xF) 220 | { 221 | case 1: 222 | fixmdatapointers((NJS_MDATA1*)motion->mdata, motionbase, modelcount, vertoff, normoff); 223 | break; 224 | case 2: 225 | fixmdatapointers((NJS_MDATA2*)motion->mdata, motionbase, modelcount, vertoff, normoff); 226 | break; 227 | case 3: 228 | fixmdatapointers((NJS_MDATA3*)motion->mdata, motionbase, modelcount, vertoff, normoff); 229 | break; 230 | case 4: 231 | fixmdatapointers((NJS_MDATA4*)motion->mdata, motionbase, modelcount, vertoff, normoff); 232 | break; 233 | case 5: 234 | fixmdatapointers((NJS_MDATA5*)motion->mdata, motionbase, modelcount, vertoff, normoff); 235 | break; 236 | default: 237 | throw; 238 | } 239 | } 240 | } 241 | 242 | fixedpointers.clear(); 243 | if (version < 2) 244 | { 245 | string label = getstring(stream); 246 | labels1[motion] = label; 247 | labels2[label] = motion; 248 | } 249 | else 250 | { 251 | uint32_t chunktype; 252 | readdata(stream, chunktype); 253 | while (chunktype != ChunkTypes_End) 254 | { 255 | uint32_t chunksz; 256 | readdata(stream, chunksz); 257 | auto chunkbase = stream.tellg(); 258 | auto nextchunk = chunkbase + (streamoff)chunksz; 259 | switch (chunktype) 260 | { 261 | case ChunkTypes_Label: 262 | while (true) 263 | { 264 | void* dataptr; 265 | readdata(stream, dataptr); 266 | uint32_t labelptr; 267 | readdata(stream, labelptr); 268 | if (dataptr == (void*)-1 && labelptr == UINT32_MAX) 269 | break; 270 | dataptr = (uint8_t*)dataptr + motionbase; 271 | tmpaddr = (uint32_t)stream.tellg(); 272 | stream.seekg((uint32_t)chunkbase + labelptr); 273 | string label = getstring(stream); 274 | stream.seekg(tmpaddr); 275 | labels1[dataptr] = label; 276 | labels2[label] = dataptr; 277 | } 278 | break; 279 | } 280 | stream.seekg(nextchunk); 281 | readdata(stream, chunktype); 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /libmodutils/AnimationFile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "ninja.h" 8 | 9 | class AnimationFile 10 | { 11 | public: 12 | AnimationFile(const char *filename); 13 | AnimationFile(const wchar_t *filename); 14 | AnimationFile(const std::string &filename); 15 | AnimationFile(const std::wstring &filename); 16 | AnimationFile(std::istream &stream); 17 | 18 | NJS_MOTION *getmotion() const; 19 | int getmodelcount() const; 20 | bool isshortrot() const; 21 | const std::string &getlabel(); 22 | const std::string& getlabel(void* data); 23 | void* getdata(const std::string& label); 24 | const std::unordered_map* getlabels() const; 25 | 26 | private: 27 | static const uint64_t SAANIM = 0x4D494E414153u; 28 | static const uint64_t FormatMask = 0xFFFFFFFFFFFFu; 29 | static const uint8_t CurrentVersion = 2; 30 | static const int headersize = 0x14; 31 | 32 | NJS_MOTION *motion; 33 | int modelcount; 34 | bool shortrot; 35 | std::unordered_map labels1; 36 | std::unordered_map labels2; 37 | std::vector> allocatedmem; 38 | std::unordered_set fixedpointers; 39 | 40 | enum ChunkTypes : uint32_t 41 | { 42 | ChunkTypes_Label = 0x4C42414C, 43 | ChunkTypes_End = 0x444E45 44 | }; 45 | 46 | void init(std::istream &stream); 47 | }; -------------------------------------------------------------------------------- /libmodutils/GameObject.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "GameObject.h" 3 | 4 | 5 | GameObject::GameObject(int index, const char *name) 6 | { 7 | objData = AllocateObjectMaster(CallMain, index, name); 8 | if (!objData) 9 | { 10 | objData->DisplaySub = CallDisplay; 11 | objData->DeleteSub = CallDelete; 12 | objData->Data2.Undefined = this; 13 | } 14 | } 15 | 16 | GameObject::GameObject(ObjectMaster *obj) 17 | { 18 | objData = obj; 19 | objData->MainSub = CallMain; 20 | objData->DisplaySub = CallDisplay; 21 | objData->DeleteSub = CallDelete; 22 | objData->Data2.Undefined = this; 23 | initFromObj = true; 24 | } 25 | 26 | void GameObject::CallMain(ObjectMaster *obj) 27 | { 28 | ((GameObject *)obj->Data2.Undefined)->Main(); 29 | } 30 | 31 | void GameObject::CallDisplay(ObjectMaster *obj) 32 | { 33 | ((GameObject *)obj->Data2.Undefined)->Display(); 34 | } 35 | 36 | void GameObject::CallDelete(ObjectMaster *obj) 37 | { 38 | GameObject *cgo = (GameObject *)obj->Data2.Undefined; 39 | cgo->Delete(); 40 | obj->Data2.Undefined = nullptr; 41 | cgo->objData = nullptr; 42 | if (cgo->initFromObj) 43 | delete cgo; 44 | } 45 | 46 | void GameObject::Display(){} 47 | 48 | void GameObject::Delete(){} 49 | 50 | const char *GameObject::GetName(){ return objData->Name; } 51 | 52 | SETObjectData *GameObject::GetSETData(){ return objData->SETData; } 53 | 54 | GameObject::~GameObject() 55 | { 56 | if (objData) 57 | { 58 | Delete(); 59 | objData->Data2.Undefined = nullptr; 60 | objData->DeleteSub = nullptr; 61 | DeleteObject_(objData); 62 | } 63 | } 64 | 65 | GameEntity::GameEntity(int index, const char *name) :GameObject(index, name) 66 | { 67 | objData->Data1.Entity = AllocateEntityData1(); 68 | } 69 | 70 | EntityData1 *GameEntity::GetData() { return objData->Data1.Entity; } 71 | 72 | char GameEntity::GetAction() { return GetData()->Action; } 73 | 74 | void GameEntity::SetAction(char action) { GetData()->Action = action; } 75 | 76 | Rotation &GameEntity::GetRotation() { return GetData()->Rotation; } 77 | 78 | typedef Rotation rot; // bleh 79 | void GameEntity::SetRotation(rot &rotation) { GetData()->Rotation = rotation; } 80 | 81 | NJS_VECTOR &GameEntity::GetPosition() { return GetData()->Position; } 82 | 83 | void GameEntity::SetPosition(NJS_VECTOR &position) { GetData()->Position = position; } 84 | 85 | NJS_VECTOR &GameEntity::GetScale() { return GetData()->Scale; } 86 | 87 | void GameEntity::SetScale(NJS_VECTOR &scale) { GetData()->Scale = scale; } 88 | 89 | CollisionInfo *GameEntity::GetCollisionInfo() { return GetData()->Collision; } -------------------------------------------------------------------------------- /libmodutils/GameObject.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "SA2ModLoader.h" 3 | class GameObject 4 | { 5 | private: 6 | static void CallMain(ObjectMaster *obj); 7 | static void CallDisplay(ObjectMaster *obj); 8 | static void CallDelete(ObjectMaster *obj); 9 | bool initFromObj; 10 | protected: 11 | ObjectMaster *objData; 12 | GameObject(int index, const char *name = "GameObject"); 13 | GameObject(ObjectMaster *obj); 14 | virtual void Delete(); 15 | public: 16 | ~GameObject(); 17 | virtual void Main() = 0; 18 | virtual void Display(); 19 | template 20 | static void Load(ObjectMaster *obj) 21 | { 22 | T *t = new T(obj); 23 | t->Main(); 24 | } 25 | const char *GetName(); 26 | SETObjectData *GetSETData(); 27 | // MSVC-specific property emulation. 28 | #ifdef _MSC_VER 29 | __declspec(property(get = GetName)) const char *Name; 30 | __declspec(property(get = GetSETData)) SETObjectData *SETData; 31 | #endif 32 | }; 33 | 34 | class GameEntity : GameObject 35 | { 36 | protected: 37 | EntityData1 *GetData(); 38 | public: 39 | GameEntity(int index, const char *name = "GameEntity"); 40 | char GetAction(); 41 | void SetAction(char action); 42 | Rotation &GetRotation(); 43 | void SetRotation(Rotation &rotation); 44 | NJS_VECTOR &GetPosition(); 45 | void SetPosition(NJS_VECTOR &position); 46 | NJS_VECTOR &GetScale(); 47 | void SetScale(NJS_VECTOR &scale); 48 | CollisionInfo *GetCollisionInfo(); 49 | // MSVC-specific property emulation. 50 | #ifdef _MSC_VER 51 | __declspec(property(get = GetAction, put = SetAction)) char Action; 52 | __declspec(property(get = GetRotation, put = SetRotation)) Rotation &Rotation; 53 | __declspec(property(get = GetPosition, put = SetPosition)) NJS_VECTOR &Position; 54 | __declspec(property(get = GetScale, put = SetScale)) NJS_VECTOR &Scale; 55 | __declspec(property(get = GetCollisionInfo)) CollisionInfo *CollisionInfo; 56 | #endif 57 | }; -------------------------------------------------------------------------------- /libmodutils/LandTableInfo.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "LandTableInfo.h" 3 | #include 4 | #include 5 | using std::default_delete; 6 | using std::ifstream; 7 | using std::ios; 8 | using std::istream; 9 | using std::list; 10 | using std::shared_ptr; 11 | using std::streamoff; 12 | using std::string; 13 | #ifdef _MSC_VER 14 | using std::wstring; 15 | #endif /* _MSC_VER */ 16 | 17 | LandTableInfo::LandTableInfo(const char *filename) 18 | { 19 | ifstream str(filename, ios::binary); 20 | init(str); 21 | str.close(); 22 | } 23 | 24 | LandTableInfo::LandTableInfo(const wchar_t *filename) 25 | { 26 | ifstream str(filename, ios::binary); 27 | init(str); 28 | str.close(); 29 | } 30 | 31 | #ifdef _MSC_VER 32 | LandTableInfo::LandTableInfo(const string &filename) 33 | { 34 | ifstream str(filename, ios::binary); 35 | init(str); 36 | str.close(); 37 | } 38 | 39 | LandTableInfo::LandTableInfo(const wstring &filename) 40 | { 41 | ifstream str(filename, ios::binary); 42 | init(str); 43 | str.close(); 44 | } 45 | #endif /* _MSC_VER */ 46 | 47 | LandTableInfo::LandTableInfo(istream &stream) { init(stream); } 48 | 49 | LandTableFormat LandTableInfo::getformat() { return format; } 50 | 51 | LandTable *LandTableInfo::getlandtable() { return landtable; } 52 | 53 | const string &LandTableInfo::getauthor() { return author; } 54 | 55 | const string &LandTableInfo::gettool() { return tool; } 56 | 57 | const string &LandTableInfo::getdescription() { return description; } 58 | 59 | const uint8_t *LandTableInfo::getmetadata(uint32_t identifier, uint32_t &size) 60 | { 61 | auto elem = metadata.find(identifier); 62 | if (elem == metadata.end()) 63 | { 64 | size = 0; 65 | return nullptr; 66 | } 67 | else 68 | { 69 | size = elem->second.size; 70 | return elem->second.data; 71 | } 72 | } 73 | 74 | static const string empty; 75 | const string &LandTableInfo::getlabel(void *data) 76 | { 77 | auto elem = labels1.find(data); 78 | if (elem == labels1.end()) 79 | return empty; 80 | else 81 | return elem->second; 82 | } 83 | 84 | void *LandTableInfo::getdata(const string &label) 85 | { 86 | auto elem = labels2.find(label); 87 | if (elem == labels2.end()) 88 | return nullptr; 89 | else 90 | return elem->second; 91 | } 92 | 93 | const std::unordered_map* LandTableInfo::getlabels() const 94 | { 95 | return &labels2; 96 | } 97 | 98 | static string getstring(istream &stream) 99 | { 100 | auto start = stream.tellg(); 101 | while (stream.get() != 0) 102 | ; 103 | auto size = stream.tellg() - start; 104 | char *buf = new char[(unsigned int)size]; 105 | stream.seekg(start); 106 | stream.read(buf, size); 107 | string result(buf); 108 | delete[] buf; 109 | return result; 110 | } 111 | 112 | template 113 | static inline void fixptr(T *&ptr, intptr_t base) 114 | { 115 | if (ptr != nullptr) 116 | ptr = (T *)((intptr_t)ptr + base); 117 | } 118 | 119 | void LandTableInfo::fixbasicmodelpointers(NJS_MODEL *model, intptr_t base) 120 | { 121 | fixptr(model->points, base); 122 | fixptr(model->normals, base); 123 | if (model->meshsets != nullptr) 124 | { 125 | fixptr(model->meshsets, base); 126 | if (fixedpointers.find(model->meshsets) == fixedpointers.end()) 127 | { 128 | fixedpointers.insert(model->meshsets); 129 | for (int i = 0; i < model->nbMeshset; i++) 130 | { 131 | fixptr(model->meshsets[i].meshes, base); 132 | fixptr(model->meshsets[i].attrs, base); 133 | fixptr(model->meshsets[i].normals, base); 134 | fixptr(model->meshsets[i].vertcolor, base); 135 | fixptr(model->meshsets[i].vertuv, base); 136 | } 137 | } 138 | } 139 | fixptr(model->mats, base); 140 | } 141 | 142 | void LandTableInfo::fixchunkmodelpointers(NJS_CNK_MODEL *model, intptr_t base) 143 | { 144 | fixptr(model->vlist, base); 145 | fixptr(model->plist, base); 146 | } 147 | 148 | void LandTableInfo::fixsa2bmodelpointers(SA2B_Model* model, intptr_t base) 149 | { 150 | if (model->Vertices != nullptr) 151 | { 152 | fixptr(model->Vertices, base); 153 | if (fixedpointers.find(model->Vertices) == fixedpointers.end()) 154 | { 155 | fixedpointers.insert(model->Vertices); 156 | for (SA2B_VertexData* vd = model->Vertices; vd->DataType != -1; ++vd) 157 | fixptr(vd->Data, base); 158 | } 159 | } 160 | if (model->OpaqueGeoData != nullptr) 161 | { 162 | fixptr(model->OpaqueGeoData, base); 163 | if (fixedpointers.find(model->OpaqueGeoData) == fixedpointers.end()) 164 | { 165 | fixedpointers.insert(model->OpaqueGeoData); 166 | for (int i = 0; i < model->OpaqueGeometryCount; i++) 167 | { 168 | fixptr(model->OpaqueGeoData[i].PrimitiveOffset, base); 169 | fixptr(model->OpaqueGeoData[i].ParameterOffset, base); 170 | } 171 | } 172 | } 173 | if (model->TranslucentGeoData != nullptr) 174 | { 175 | fixptr(model->TranslucentGeoData, base); 176 | if (fixedpointers.find(model->TranslucentGeoData) == fixedpointers.end()) 177 | { 178 | fixedpointers.insert(model->TranslucentGeoData); 179 | for (int i = 0; i < model->TranslucentGeometryCount; i++) 180 | { 181 | fixptr(model->TranslucentGeoData[i].PrimitiveOffset, base); 182 | fixptr(model->TranslucentGeoData[i].ParameterOffset, base); 183 | } 184 | } 185 | } 186 | } 187 | 188 | void LandTableInfo::fixobjectpointers(NJS_OBJECT *object, intptr_t base, bool chunk) 189 | { 190 | if (object->model != nullptr) 191 | { 192 | fixptr(object->model, base); 193 | if (fixedpointers.find(object->model) == fixedpointers.end()) 194 | { 195 | fixedpointers.insert(object->model); 196 | if (chunk) 197 | switch (format) 198 | { 199 | case LandTableFormat_SA2: 200 | fixchunkmodelpointers(object->chunkmodel, base); 201 | break; 202 | case LandTableFormat_SA2B: 203 | fixsa2bmodelpointers(object->sa2bmodel, base); 204 | break; 205 | } 206 | else 207 | fixbasicmodelpointers(object->basicmodel, base); 208 | } 209 | } 210 | if (object->child != nullptr) 211 | { 212 | fixptr(object->child, base); 213 | if (fixedpointers.find(object->child) == fixedpointers.end()) 214 | { 215 | fixedpointers.insert(object->child); 216 | fixobjectpointers(object->child, base, chunk); 217 | } 218 | } 219 | if (object->sibling != nullptr) 220 | { 221 | fixptr(object->sibling, base); 222 | if (fixedpointers.find(object->sibling) == fixedpointers.end()) 223 | { 224 | fixedpointers.insert(object->sibling); 225 | fixobjectpointers(object->sibling, base, chunk); 226 | } 227 | } 228 | } 229 | 230 | void LandTableInfo::fixlandtablepointers(LandTable *landtable, intptr_t base) 231 | { 232 | if (landtable->COLList != nullptr) 233 | { 234 | fixptr(landtable->COLList, base); 235 | for (int i = 0; i < landtable->COLCount; i++) 236 | if (landtable->COLList[i].Model != nullptr) 237 | { 238 | fixptr(landtable->COLList[i].Model, base); 239 | if (fixedpointers.find(landtable->COLList[i].Model) == fixedpointers.end()) 240 | { 241 | fixedpointers.insert(landtable->COLList[i].Model); 242 | bool chunk = false; 243 | if (landtable->VisibleModelCount != -1) 244 | chunk = i < landtable->VisibleModelCount; 245 | else 246 | chunk = landtable->COLList[i].Flags < 0; 247 | fixobjectpointers(landtable->COLList[i].Model, base, chunk); 248 | } 249 | } 250 | } 251 | fixptr(landtable->TextureName, base); 252 | } 253 | 254 | template 255 | static inline void readdata(istream &stream, T &data) 256 | { 257 | stream.read((char *)&data, sizeof(T)); 258 | } 259 | 260 | void LandTableInfo::init(istream &stream) 261 | { 262 | uint64_t magic; 263 | readdata(stream, magic); 264 | uint8_t version = magic >> 56; 265 | magic &= FormatMask; 266 | if (version != CurrentVersion) // unrecognized file version 267 | return; 268 | switch (magic) 269 | { 270 | case SA2LVL: 271 | format = LandTableFormat_SA2; 272 | break; 273 | case SA2BLVL: 274 | format = LandTableFormat_SA2B; 275 | break; 276 | default: 277 | return; 278 | } 279 | uint32_t landtableoff; 280 | readdata(stream, landtableoff); 281 | landtableoff -= headersize; 282 | uint32_t tmpaddr; 283 | readdata(stream, tmpaddr); 284 | int mdlsize = tmpaddr - headersize; 285 | uint8_t *landtablebuf = new uint8_t[mdlsize]; 286 | allocatedmem.push_back(shared_ptr(landtablebuf, default_delete())); 287 | stream.read((char *)landtablebuf, mdlsize); 288 | landtable = (LandTable *)(landtablebuf + landtableoff); 289 | intptr_t landtablebase = (intptr_t)landtablebuf - headersize; 290 | fixlandtablepointers(landtable, landtablebase); 291 | fixedpointers.clear(); 292 | uint32_t chunktype; 293 | readdata(stream, chunktype); 294 | while (chunktype != ChunkTypes_End) 295 | { 296 | uint32_t chunksz; 297 | readdata(stream, chunksz); 298 | auto chunkbase = stream.tellg(); 299 | auto nextchunk = chunkbase + (streamoff)chunksz; 300 | switch (chunktype) 301 | { 302 | case ChunkTypes_Label: 303 | while (true) 304 | { 305 | void *dataptr; 306 | readdata(stream, dataptr); 307 | uint32_t labelptr; 308 | readdata(stream, labelptr); 309 | if (dataptr == (void *)-1 && labelptr == UINT32_MAX) 310 | break; 311 | dataptr = (uint8_t *)dataptr + landtablebase; 312 | tmpaddr = (uint32_t)stream.tellg(); 313 | stream.seekg((uint32_t)chunkbase + labelptr); 314 | string label = getstring(stream); 315 | stream.seekg(tmpaddr); 316 | labels1[dataptr] = label; 317 | labels2[label] = dataptr; 318 | } 319 | break; 320 | case ChunkTypes_Author: 321 | author = getstring(stream); 322 | break; 323 | case ChunkTypes_Tool: 324 | tool = getstring(stream); 325 | break; 326 | case ChunkTypes_Description: 327 | description = getstring(stream); 328 | break; 329 | default: 330 | uint8_t *buf = new uint8_t[chunksz]; 331 | allocatedmem.push_back(shared_ptr(buf, default_delete())); 332 | stream.read((char *)buf, chunksz); 333 | Metadata meta = { chunksz, buf }; 334 | metadata[chunktype] = meta; 335 | break; 336 | } 337 | stream.seekg(nextchunk); 338 | readdata(stream, chunktype); 339 | } 340 | } -------------------------------------------------------------------------------- /libmodutils/LandTableInfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "SA2ModLoader.h" 8 | 9 | enum LandTableFormat 10 | { 11 | LandTableFormat_Invalid, 12 | LandTableFormat_SA2, 13 | LandTableFormat_SA2B 14 | }; 15 | 16 | class LandTableInfo 17 | { 18 | public: 19 | struct Metadata { uint32_t size; const uint8_t* data; }; 20 | 21 | LandTableInfo(const char* filename); 22 | LandTableInfo(const wchar_t* filename); 23 | LandTableInfo(const std::string& filename); 24 | LandTableInfo(const std::wstring& filename); 25 | LandTableInfo(std::istream& stream); 26 | 27 | LandTableFormat getformat(); 28 | LandTable* getlandtable(); 29 | const std::string& getauthor(); 30 | const std::string& gettool(); 31 | const std::string& getdescription(); 32 | const uint8_t* getmetadata(uint32_t identifier, uint32_t& size); 33 | const std::string& getlabel(void* data); 34 | const std::unordered_map* getlabels() const; 35 | void* getdata(const std::string& label); 36 | 37 | private: 38 | static const uint64_t SA2LVL = 0x4C564C324153u; 39 | static const uint64_t SA2BLVL = 0x4C564C42324153u; 40 | static const uint64_t FormatMask = 0xFFFFFFFFFFFFFFu; 41 | static const uint8_t CurrentVersion = 3; 42 | static const int headersize = 0x10; 43 | 44 | LandTableFormat format; 45 | LandTable* landtable; 46 | std::string author, tool, description; 47 | std::unordered_map metadata; 48 | std::unordered_map labels1; 49 | std::unordered_map labels2; 50 | std::vector> allocatedmem; 51 | std::unordered_set fixedpointers; 52 | 53 | enum ChunkTypes : uint32_t 54 | { 55 | ChunkTypes_Label = 0x4C42414C, 56 | ChunkTypes_Author = 0x48545541, 57 | ChunkTypes_Tool = 0x4C4F4F54, 58 | ChunkTypes_Description = 0x43534544, 59 | ChunkTypes_End = 0x444E45 60 | }; 61 | 62 | void fixbasicmodelpointers(NJS_MODEL* model, intptr_t base); 63 | void fixchunkmodelpointers(NJS_CNK_MODEL* model, intptr_t base); 64 | void fixsa2bmodelpointers(SA2B_Model* model, intptr_t base); 65 | void fixobjectpointers(NJS_OBJECT* object, intptr_t base, bool chunk); 66 | void fixlandtablepointers(LandTable* landtable, intptr_t base); 67 | void init(std::istream& stream); 68 | }; -------------------------------------------------------------------------------- /libmodutils/ModelInfo.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "ModelInfo.h" 3 | #include 4 | #include 5 | using std::default_delete; 6 | using std::ifstream; 7 | using std::ios; 8 | using std::istream; 9 | using std::list; 10 | using std::shared_ptr; 11 | using std::streamoff; 12 | using std::string; 13 | #ifdef _MSC_VER 14 | using std::wstring; 15 | #endif /* _MSC_VER */ 16 | 17 | ModelInfo::ModelInfo(const char* filename) 18 | { 19 | ifstream str(filename, ios::binary); 20 | init(str); 21 | str.close(); 22 | } 23 | 24 | ModelInfo::ModelInfo(const wchar_t* filename) 25 | { 26 | ifstream str(filename, ios::binary); 27 | init(str); 28 | str.close(); 29 | } 30 | 31 | ModelInfo::ModelInfo(const string& filename) 32 | { 33 | ifstream str(filename, ios::binary); 34 | init(str); 35 | str.close(); 36 | } 37 | 38 | ModelInfo::ModelInfo(const wstring& filename) 39 | { 40 | ifstream str(filename, ios::binary); 41 | init(str); 42 | str.close(); 43 | } 44 | 45 | ModelInfo::ModelInfo(istream& stream) { init(stream); } 46 | 47 | ModelFormat ModelInfo::getformat() { return format; } 48 | 49 | NJS_OBJECT* ModelInfo::getmodel() { return model; } 50 | 51 | const string& ModelInfo::getauthor() { return author; } 52 | 53 | const string& ModelInfo::gettool() { return tool; } 54 | 55 | const string& ModelInfo::getdescription() { return description; } 56 | 57 | const uint8_t* ModelInfo::getmetadata(uint32_t identifier, uint32_t& size) 58 | { 59 | auto elem = metadata.find(identifier); 60 | if (elem == metadata.end()) 61 | { 62 | size = 0; 63 | return nullptr; 64 | } 65 | else 66 | { 67 | size = elem->second.size; 68 | return elem->second.data; 69 | } 70 | } 71 | 72 | static const string empty; 73 | const string& ModelInfo::getlabel(void* data) 74 | { 75 | auto elem = labels1.find(data); 76 | if (elem == labels1.end()) 77 | return empty; 78 | else 79 | return elem->second; 80 | } 81 | 82 | void* ModelInfo::getdata(const string& label) 83 | { 84 | auto elem = labels2.find(label); 85 | if (elem == labels2.end()) 86 | return nullptr; 87 | else 88 | return elem->second; 89 | } 90 | 91 | const std::unordered_map* ModelInfo::getlabels() const 92 | { 93 | return &labels2; 94 | } 95 | 96 | const list& ModelInfo::getanimations() { return animations; } 97 | 98 | const list& ModelInfo::getmorphs() { return morphs; } 99 | 100 | static string getstring(istream& stream) 101 | { 102 | auto start = stream.tellg(); 103 | while (stream.get() != 0) 104 | ; 105 | auto size = stream.tellg() - start; 106 | char* buf = new char[(unsigned int)size]; 107 | stream.seekg(start); 108 | stream.read(buf, size); 109 | string result(buf); 110 | delete[] buf; 111 | return result; 112 | } 113 | 114 | template 115 | static inline void fixptr(T*& ptr, intptr_t base) 116 | { 117 | if (ptr != nullptr) 118 | ptr = (T*)((intptr_t)ptr + base); 119 | } 120 | 121 | void ModelInfo::fixbasicmodelpointers(NJS_MODEL* model, intptr_t base) 122 | { 123 | fixptr(model->points, base); 124 | fixptr(model->normals, base); 125 | if (model->meshsets != nullptr) 126 | { 127 | fixptr(model->meshsets, base); 128 | if (fixedpointers.find(model->meshsets) == fixedpointers.end()) 129 | { 130 | fixedpointers.insert(model->meshsets); 131 | for (int i = 0; i < model->nbMeshset; i++) 132 | { 133 | fixptr(model->meshsets[i].meshes, base); 134 | fixptr(model->meshsets[i].attrs, base); 135 | fixptr(model->meshsets[i].normals, base); 136 | fixptr(model->meshsets[i].vertcolor, base); 137 | fixptr(model->meshsets[i].vertuv, base); 138 | } 139 | } 140 | } 141 | fixptr(model->mats, base); 142 | } 143 | 144 | void ModelInfo::fixchunkmodelpointers(NJS_CNK_MODEL* model, intptr_t base) 145 | { 146 | fixptr(model->vlist, base); 147 | fixptr(model->plist, base); 148 | } 149 | 150 | void ModelInfo::fixsa2bmodelpointers(SA2B_Model* model, intptr_t base) 151 | { 152 | if (model->Vertices != nullptr) 153 | { 154 | fixptr(model->Vertices, base); 155 | if (fixedpointers.find(model->Vertices) == fixedpointers.end()) 156 | { 157 | fixedpointers.insert(model->Vertices); 158 | for (SA2B_VertexData* vd = model->Vertices; vd->DataType != -1; ++vd) 159 | fixptr(vd->Data, base); 160 | } 161 | } 162 | if (model->OpaqueGeoData != nullptr) 163 | { 164 | fixptr(model->OpaqueGeoData, base); 165 | if (fixedpointers.find(model->OpaqueGeoData) == fixedpointers.end()) 166 | { 167 | fixedpointers.insert(model->OpaqueGeoData); 168 | for (int i = 0; i < model->OpaqueGeometryCount; i++) 169 | { 170 | fixptr(model->OpaqueGeoData[i].PrimitiveOffset, base); 171 | fixptr(model->OpaqueGeoData[i].ParameterOffset, base); 172 | } 173 | } 174 | } 175 | if (model->TranslucentGeoData != nullptr) 176 | { 177 | fixptr(model->TranslucentGeoData, base); 178 | if (fixedpointers.find(model->TranslucentGeoData) == fixedpointers.end()) 179 | { 180 | fixedpointers.insert(model->TranslucentGeoData); 181 | for (int i = 0; i < model->TranslucentGeometryCount; i++) 182 | { 183 | fixptr(model->TranslucentGeoData[i].PrimitiveOffset, base); 184 | fixptr(model->TranslucentGeoData[i].ParameterOffset, base); 185 | } 186 | } 187 | } 188 | } 189 | 190 | void ModelInfo::fixobjectpointers(NJS_OBJECT* object, intptr_t base) 191 | { 192 | if (object->model != nullptr) 193 | { 194 | fixptr(object->model, base); 195 | if (fixedpointers.find(object->model) == fixedpointers.end()) 196 | { 197 | fixedpointers.insert(object->model); 198 | if (format == ModelFormat_Basic) 199 | fixbasicmodelpointers(object->basicmodel, base); 200 | else if (format == ModelFormat_Chunk) 201 | fixchunkmodelpointers(object->chunkmodel, base); 202 | else if (format == ModelFormat_SA2B) 203 | fixsa2bmodelpointers(object->sa2bmodel, base); 204 | } 205 | } 206 | if (object->child != nullptr) 207 | { 208 | fixptr(object->child, base); 209 | if (fixedpointers.find(object->child) == fixedpointers.end()) 210 | { 211 | fixedpointers.insert(object->child); 212 | fixobjectpointers(object->child, base); 213 | } 214 | } 215 | if (object->sibling != nullptr) 216 | { 217 | fixptr(object->sibling, base); 218 | if (fixedpointers.find(object->sibling) == fixedpointers.end()) 219 | { 220 | fixedpointers.insert(object->sibling); 221 | fixobjectpointers(object->sibling, base); 222 | } 223 | } 224 | } 225 | 226 | template 227 | static inline void readdata(istream& stream, T& data) 228 | { 229 | stream.read((char*)&data, sizeof(T)); 230 | } 231 | 232 | void ModelInfo::init(istream& stream) 233 | { 234 | uint64_t magic; 235 | readdata(stream, magic); 236 | uint8_t version = magic >> 56; 237 | magic &= FormatMask; 238 | if (version != CurrentVersion) // unrecognized file version 239 | return; 240 | switch (magic) 241 | { 242 | case SA1MDL: 243 | format = ModelFormat_Basic; 244 | break; 245 | case SA2MDL: 246 | format = ModelFormat_Chunk; 247 | break; 248 | case SA2BMDL: 249 | format = ModelFormat_SA2B; 250 | break; 251 | default: 252 | return; 253 | } 254 | uint32_t modeloff; 255 | readdata(stream, modeloff); 256 | modeloff -= headersize; 257 | uint32_t tmpaddr; 258 | readdata(stream, tmpaddr); 259 | int mdlsize = tmpaddr - headersize; 260 | uint8_t* modelbuf = new uint8_t[mdlsize]; 261 | allocatedmem.push_back(shared_ptr(modelbuf, default_delete())); 262 | stream.read((char*)modelbuf, mdlsize); 263 | model = (NJS_OBJECT*)(modelbuf + modeloff); 264 | intptr_t modelbase = (intptr_t)modelbuf - headersize; 265 | fixobjectpointers(model, modelbase); 266 | fixedpointers.clear(); 267 | uint32_t chunktype; 268 | readdata(stream, chunktype); 269 | while (chunktype != ChunkTypes_End) 270 | { 271 | uint32_t chunksz; 272 | readdata(stream, chunksz); 273 | auto chunkbase = stream.tellg(); 274 | auto nextchunk = chunkbase + (streamoff)chunksz; 275 | switch (chunktype) 276 | { 277 | case ChunkTypes_Label: 278 | while (true) 279 | { 280 | void* dataptr; 281 | readdata(stream, dataptr); 282 | uint32_t labelptr; 283 | readdata(stream, labelptr); 284 | if (dataptr == (void*)-1 && labelptr == UINT32_MAX) 285 | break; 286 | dataptr = (uint8_t*)dataptr + modelbase; 287 | tmpaddr = (uint32_t)stream.tellg(); 288 | stream.seekg((uint32_t)chunkbase + labelptr); 289 | string label = getstring(stream); 290 | stream.seekg(tmpaddr); 291 | labels1[dataptr] = label; 292 | labels2[label] = dataptr; 293 | } 294 | break; 295 | case ChunkTypes_Animation: 296 | while (true) 297 | { 298 | uint32_t labelptr; 299 | readdata(stream, labelptr); 300 | if (labelptr == UINT32_MAX) 301 | break; 302 | tmpaddr = (uint32_t)stream.tellg(); 303 | stream.seekg((uint32_t)chunkbase + labelptr); 304 | animations.push_back(getstring(stream)); 305 | stream.seekg(tmpaddr); 306 | } 307 | break; 308 | case ChunkTypes_Morph: 309 | while (true) 310 | { 311 | uint32_t labelptr; 312 | readdata(stream, labelptr); 313 | if (labelptr == UINT32_MAX) 314 | break; 315 | tmpaddr = (uint32_t)stream.tellg(); 316 | stream.seekg((uint32_t)chunkbase + labelptr); 317 | morphs.push_back(getstring(stream)); 318 | stream.seekg(tmpaddr); 319 | } 320 | break; 321 | case ChunkTypes_Author: 322 | author = getstring(stream); 323 | break; 324 | case ChunkTypes_Tool: 325 | tool = getstring(stream); 326 | break; 327 | case ChunkTypes_Description: 328 | description = getstring(stream); 329 | break; 330 | default: 331 | uint8_t* buf = new uint8_t[chunksz]; 332 | allocatedmem.push_back(shared_ptr(buf, default_delete())); 333 | stream.read((char*)buf, chunksz); 334 | Metadata meta = { chunksz, buf }; 335 | metadata[chunktype] = meta; 336 | break; 337 | } 338 | stream.seekg(nextchunk); 339 | readdata(stream, chunktype); 340 | } 341 | } -------------------------------------------------------------------------------- /libmodutils/ModelInfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "ninja.h" 8 | 9 | enum ModelFormat 10 | { 11 | ModelFormat_Invalid, 12 | ModelFormat_Basic, 13 | ModelFormat_Chunk, 14 | ModelFormat_SA2B 15 | }; 16 | 17 | class ModelInfo 18 | { 19 | public: 20 | struct Metadata { uint32_t size; const uint8_t* data; }; 21 | 22 | ModelInfo(const char* filename); 23 | ModelInfo(const wchar_t* filename); 24 | ModelInfo(const std::string& filename); 25 | ModelInfo(const std::wstring& filename); 26 | ModelInfo(std::istream& stream); 27 | 28 | ModelFormat getformat(); 29 | NJS_OBJECT* getmodel(); 30 | const std::string& getauthor(); 31 | const std::string& gettool(); 32 | const std::string& getdescription(); 33 | const uint8_t* getmetadata(uint32_t identifier, uint32_t& size); 34 | const std::string& getlabel(void* data); 35 | void* getdata(const std::string& label); 36 | const std::unordered_map* getlabels() const; 37 | const std::list& getanimations(); 38 | const std::list& getmorphs(); 39 | 40 | private: 41 | static const uint64_t SA1MDL = 0x4C444D314153u; 42 | static const uint64_t SA2MDL = 0x4C444D324153u; 43 | static const uint64_t SA2BMDL = 0x4C444D42324153u; 44 | static const uint64_t FormatMask = 0xFFFFFFFFFFFFFFu; 45 | static const uint8_t CurrentVersion = 3; 46 | static const int headersize = 0x10; 47 | 48 | ModelFormat format; 49 | NJS_OBJECT* model; 50 | std::string author, tool, description; 51 | std::unordered_map metadata; 52 | std::unordered_map labels1; 53 | std::unordered_map labels2; 54 | std::vector>allocatedmem; 55 | std::unordered_setfixedpointers; 56 | std::list animations, morphs; 57 | 58 | enum ChunkTypes : uint32_t 59 | { 60 | ChunkTypes_Label = 0x4C42414C, 61 | ChunkTypes_Animation = 0x4D494E41, 62 | ChunkTypes_Morph = 0x46524F4D, 63 | ChunkTypes_Author = 0x48545541, 64 | ChunkTypes_Tool = 0x4C4F4F54, 65 | ChunkTypes_Description = 0x43534544, 66 | ChunkTypes_Texture = 0x584554, 67 | ChunkTypes_End = 0x444E45 68 | }; 69 | 70 | void fixbasicmodelpointers(NJS_MODEL* model, intptr_t base); 71 | void fixchunkmodelpointers(NJS_CNK_MODEL* model, intptr_t base); 72 | void fixsa2bmodelpointers(SA2B_Model* model, intptr_t base); 73 | void fixobjectpointers(NJS_OBJECT* object, intptr_t base); 74 | void init(std::istream& stream); 75 | }; -------------------------------------------------------------------------------- /libmodutils/PAKFile.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include 3 | #include "PAKFile.h" 4 | 5 | PAKFile::PAKFile(const char* filename) 6 | { 7 | std::ifstream str(filename, std::ios::binary); 8 | init(str); 9 | str.close(); 10 | } 11 | 12 | PAKFile::PAKFile(const wchar_t* filename) 13 | { 14 | std::ifstream str(filename, std::ios::binary); 15 | init(str); 16 | str.close(); 17 | } 18 | 19 | PAKFile::PAKFile(const std::string& filename) 20 | { 21 | std::ifstream str(filename, std::ios::binary); 22 | init(str); 23 | str.close(); 24 | } 25 | 26 | PAKFile::PAKFile(const std::wstring& filename) 27 | { 28 | std::ifstream str(filename, std::ios::binary); 29 | init(str); 30 | str.close(); 31 | } 32 | 33 | PAKFile::PAKFile(std::istream& stream) 34 | { 35 | init(stream); 36 | } 37 | 38 | PAKFile::~PAKFile() 39 | { 40 | for (auto& entry : entries) 41 | delete[] entry.second.data; 42 | } 43 | 44 | template 45 | static inline void readdata(std::istream& stream, T& data) 46 | { 47 | stream.read(reinterpret_cast(&data), sizeof(T)); 48 | } 49 | 50 | bool PAKFile::is_pak(std::istream& file) 51 | { 52 | const auto pos = file.tellg(); 53 | const auto result = check_header(file); 54 | file.seekg(pos); 55 | 56 | return result; 57 | } 58 | 59 | bool PAKFile::is_pak(const char* filename) 60 | { 61 | std::ifstream file(filename, std::ios::binary); 62 | return check_header(file); 63 | } 64 | 65 | bool PAKFile::is_pak(const wchar_t* filename) 66 | { 67 | std::ifstream file(filename, std::ios::binary); 68 | return check_header(file); 69 | } 70 | 71 | bool PAKFile::is_pak(const std::string& filename) 72 | { 73 | std::ifstream file(filename, std::ios::binary); 74 | return check_header(file); 75 | } 76 | 77 | bool PAKFile::is_pak(const std::wstring& filename) 78 | { 79 | std::ifstream file(filename, std::ios::binary); 80 | return check_header(file); 81 | } 82 | 83 | const PAKFile::datatype* PAKFile::data() const 84 | { 85 | return &entries; 86 | } 87 | 88 | PAKFile::iterator PAKFile::begin() const 89 | { 90 | return entries.begin(); 91 | } 92 | 93 | PAKFile::iterator PAKFile::end() const 94 | { 95 | return entries.end(); 96 | } 97 | 98 | const PAKFile::Entry* PAKFile::find(std::string& name) const 99 | { 100 | auto& entry = entries.find(name); 101 | if (entry == end()) 102 | return nullptr; 103 | return &entry->second; 104 | } 105 | 106 | bool PAKFile::check_header(std::istream& stream) 107 | { 108 | uint32_t magic; 109 | readdata(stream, magic); 110 | 111 | return magic == PAKFile::magic; 112 | } 113 | 114 | void PAKFile::init(std::istream& stream) 115 | { 116 | if (!check_header(stream)) 117 | return; 118 | stream.seekg(0x35, std::ios::cur); 119 | int numfiles; 120 | readdata(stream, numfiles); 121 | std::vector paths; 122 | paths.reserve(numfiles); 123 | std::vector names; 124 | names.reserve(numfiles); 125 | std::vector lens; 126 | lens.reserve(numfiles); 127 | int strlen; 128 | for (int i = 0; i < numfiles; ++i) 129 | { 130 | readdata(stream, strlen); 131 | std::string path(strlen, 0); 132 | stream.read(&path[0], strlen); 133 | paths.push_back(path); 134 | readdata(stream, strlen); 135 | std::string name(strlen, 0); 136 | stream.read(&name[0], strlen); 137 | transform(name.begin(), name.end(), name.begin(), ::tolower); 138 | names.push_back(name); 139 | int len; 140 | readdata(stream, len); 141 | lens.push_back(len); 142 | stream.seekg(4, std::ios::cur); 143 | } 144 | for (int i = 0; i < numfiles; ++i) 145 | { 146 | char* data = new char[lens[i]]; 147 | stream.read(data, lens[i]); 148 | entries.insert({ names[i], { paths[i], data, lens[i] } }); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /libmodutils/PAKFile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | class PAKFile 3 | { 4 | struct Entry 5 | { 6 | std::string long_path; 7 | void* data; 8 | int length; 9 | }; 10 | 11 | using datatype = std::unordered_map; 12 | using iterator = datatype::const_iterator; 13 | 14 | public: 15 | PAKFile(const char* filename); 16 | PAKFile(const wchar_t* filename); 17 | PAKFile(const std::string& filename); 18 | PAKFile(const std::wstring& filename); 19 | PAKFile(std::istream& stream); 20 | 21 | ~PAKFile(); 22 | 23 | static bool is_pak(const char* filename); 24 | static bool is_pak(const wchar_t* filename); 25 | static bool is_pak(const std::string& filename); 26 | static bool is_pak(const std::wstring& filename); 27 | static bool is_pak(std::istream& stream); 28 | 29 | const datatype* data() const; 30 | 31 | iterator begin() const; 32 | iterator end() const; 33 | const Entry* find(std::string& name) const; 34 | 35 | private: 36 | static const uint32_t magic = 0x6B617001; 37 | datatype entries; 38 | static bool check_header(std::istream& stream); 39 | void init(std::istream& stream); 40 | }; 41 | 42 | -------------------------------------------------------------------------------- /libmodutils/Trampoline.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "Trampoline.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | Trampoline::Trampoline(intptr_t start, intptr_t end, void* func, bool destructRevert) 11 | : target(reinterpret_cast(start)) 12 | , detour(func) 13 | , codeData(nullptr) 14 | , originalSize(0) 15 | , revert(destructRevert) 16 | { 17 | if (start > end) 18 | { 19 | throw std::exception("Start address cannot exceed end address."); 20 | } 21 | 22 | if (end - start < 5) 23 | { 24 | throw std::exception("Length cannot be less than 5 bytes."); 25 | } 26 | 27 | originalSize = end - start; 28 | 29 | // Copy original instructions 30 | codeData = VirtualAlloc(nullptr, originalSize + 5, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); 31 | 32 | if (codeData == nullptr) 33 | { 34 | throw std::exception("VirtualAlloc failure."); 35 | } 36 | 37 | // memcpy() can be used instead of ReadProcessMemory 38 | // because we're not reading memory from another process. 39 | memcpy(codeData, target, originalSize); 40 | 41 | const auto ptr = static_cast(codeData); 42 | 43 | if (ptr[0] == 0xE8) 44 | { 45 | // If the start address has a function call right off the bat, 46 | // just repair it for the sake of convenience. 47 | intptr_t addr = start + 5 + *reinterpret_cast(&ptr[1]); 48 | WriteCall(ptr, (void*)addr); 49 | } 50 | else if (ptr[0] == 0xE9) 51 | { 52 | // If an existing (hopefully) trampoline has been applied to this address, 53 | // correct the jump offset for it. 54 | intptr_t addr = start + 5 + *reinterpret_cast(&ptr[1]); 55 | WriteJump(ptr, (void*)addr); 56 | } 57 | 58 | // Append jump 59 | WriteJump(&ptr[originalSize], (void*)end); 60 | 61 | // NOP the original code. 62 | // NOTE: This is in .text, so we have to use WriteData(). 63 | // Using memset() will crash. 64 | std::vector nop(originalSize, 0x90); 65 | WriteData(target, nop.data(), nop.size()); 66 | 67 | // Write a Jump to the new target function. 68 | WriteJump(target, func); 69 | } 70 | 71 | Trampoline::~Trampoline() 72 | { 73 | if (codeData) 74 | { 75 | if (revert) 76 | { 77 | WriteData(target, codeData, originalSize); 78 | } 79 | 80 | VirtualFree(codeData, 0, MEM_RELEASE); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /libmodutils/Trampoline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // TODO: Better documentation 6 | // TODO: Clearer member names 7 | 8 | class Trampoline 9 | { 10 | private: 11 | void* target; 12 | void* detour; 13 | LPVOID codeData; 14 | size_t originalSize; 15 | const bool revert; 16 | 17 | public: 18 | /// 19 | /// Initializes a new , allowing you to replace functions and still call the original code. 20 | /// 21 | /// Start offset (address of function). 22 | /// End offset. 23 | /// Your detour function. 24 | /// If true, code changes will be reverted when this instance is destroyed. 25 | /// 26 | /// If the start address begins with a jump or call instruction, the relative address will be automatically repaired. 27 | /// Any subsequent jumps or calls caught in the range of and will need 28 | /// to be repaired manually. 29 | /// 30 | Trampoline(intptr_t start, intptr_t end, void* func, bool destructRevert = true); 31 | ~Trampoline(); 32 | 33 | // Pointer to original code. 34 | LPVOID Target() const 35 | { 36 | return codeData; 37 | } 38 | // Pointer to your detour. 39 | void* Detour() const 40 | { 41 | return detour; 42 | } 43 | // Original data size. 44 | size_t OriginalSize() const 45 | { 46 | return originalSize; 47 | } 48 | // Size of Target including appended jump to remaining original code. 49 | size_t CodeSize() const 50 | { 51 | return originalSize + 5; 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /libmodutils/libmodutils.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {83C0F6B3-2297-4A14-98D6-F9C3E99192CE} 15 | Win32Proj 16 | libmodutils 17 | 10.0 18 | 19 | 20 | 21 | StaticLibrary 22 | true 23 | v141_xp 24 | Unicode 25 | 26 | 27 | StaticLibrary 28 | false 29 | v141_xp 30 | true 31 | Unicode 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | $(SolutionDir)bin\ 47 | $(IncludePath); 48 | 49 | 50 | $(SolutionDir)bin\ 51 | 52 | 53 | 54 | Use 55 | Level3 56 | Disabled 57 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 58 | true 59 | ..\SA2ModLoader\include 60 | 61 | 62 | Windows 63 | 64 | 65 | 66 | 67 | Level3 68 | Use 69 | MaxSpeed 70 | true 71 | true 72 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 73 | true 74 | ..\SA2ModLoader\include 75 | 76 | 77 | Windows 78 | true 79 | true 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | Create 99 | Create 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /libmodutils/libmodutils.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | 41 | 42 | Source Files 43 | 44 | 45 | Source Files 46 | 47 | 48 | Source Files 49 | 50 | 51 | Source Files 52 | 53 | 54 | Source Files 55 | 56 | 57 | Source Files 58 | 59 | 60 | Source Files 61 | 62 | 63 | -------------------------------------------------------------------------------- /libmodutils/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // libmodutils.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /libmodutils/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | --------------------------------------------------------------------------------