forked from str1k3r/4JLibs-VS2012
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c50659091 | ||
|
|
fdc89a23ea | ||
|
|
e2f5f4c545 | ||
|
|
2c2c7b8075 | ||
|
|
d767a810e8 | ||
|
|
3df111d9a3 | ||
|
|
62c8f66bbe | ||
|
|
0c24c663ef | ||
|
|
842fb91443 | ||
|
|
88a26c82a6 | ||
|
|
26a297f1de | ||
|
|
a9df365c5c | ||
|
|
fdef90f8e7 |
@@ -239,9 +239,9 @@
|
|||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
<WarningLevel>Level3</WarningLevel>
|
<WarningLevel>Level3</WarningLevel>
|
||||||
<Optimization>MaxSpeed</Optimization>
|
<Optimization>Full</Optimization>
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
<IntrinsicFunctions>false</IntrinsicFunctions>
|
||||||
<PreprocessorDefinitions>_WINDOWS64;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>_WINDOWS64;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<SDLCheck>true</SDLCheck>
|
<SDLCheck>true</SDLCheck>
|
||||||
<AdditionalIncludeDirectories>../Headers/Windows64;../Headers;$(ProjectDir);$(ProjectDir)Windows64\vendor\SDL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>../Headers/Windows64;../Headers;$(ProjectDir);$(ProjectDir)Windows64\vendor\SDL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
@@ -249,6 +249,7 @@
|
|||||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||||
<CompileAs>CompileAsCpp</CompileAs>
|
<CompileAs>CompileAsCpp</CompileAs>
|
||||||
|
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Windows</SubSystem>
|
<SubSystem>Windows</SubSystem>
|
||||||
|
|||||||
@@ -165,6 +165,23 @@ void C_4JInput::GetText(uint16_t *UTF16String)
|
|||||||
InternalKeyboard.GetText(UTF16String);
|
InternalKeyboard.GetText(UTF16String);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* C_4JInput::PollControllerType(int ipad) {
|
||||||
|
const char* GamepadType = InternalInputManager.GetGamepadType(ipad);
|
||||||
|
|
||||||
|
if (!GamepadType)
|
||||||
|
return "UNKNOWN";
|
||||||
|
|
||||||
|
if (strcmp(GamepadType, "SWITCH_PRO") == 0 ||
|
||||||
|
strcmp(GamepadType, "JOYCON_PAIR") == 0 ||
|
||||||
|
strcmp(GamepadType, "JOYCON_L") == 0 ||
|
||||||
|
strcmp(GamepadType, "JOYCON_R") == 0)
|
||||||
|
{
|
||||||
|
return "SWITCH";
|
||||||
|
}
|
||||||
|
|
||||||
|
return GamepadType; // str1k3r - if not one of the switch types, return the type itself
|
||||||
|
}
|
||||||
|
|
||||||
bool C_4JInput::VerifyStrings(WCHAR **pwStringA, int iStringC, int (*Func)(LPVOID, STRING_VERIFY_RESPONSE *), LPVOID lpParam)
|
bool C_4JInput::VerifyStrings(WCHAR **pwStringA, int iStringC, int (*Func)(LPVOID, STRING_VERIFY_RESPONSE *), LPVOID lpParam)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ SOFTWARE.
|
|||||||
#include "vendor\SDL\include\SDL3\SDL.h"
|
#include "vendor\SDL\include\SDL3\SDL.h"
|
||||||
|
|
||||||
CInput InternalInputManager;
|
CInput InternalInputManager;
|
||||||
SDL_Event event;
|
|
||||||
|
|
||||||
CInput::CInput()
|
CInput::CInput()
|
||||||
{
|
{
|
||||||
@@ -277,15 +276,11 @@ void CInput::SetJoypadValues(JOYPAD *pThisPad)
|
|||||||
pThisPad->m_iLeftThumbY = sThumbLY - m_iDeadzone;
|
pThisPad->m_iLeftThumbY = sThumbLY - m_iDeadzone;
|
||||||
}
|
}
|
||||||
|
|
||||||
pThisPad->m_iNormalizedLeftThumbX = pThisPad->m_iLeftThumbX / m_iEffectiveRange;
|
//str1k3r - silly piebot this was duplicated, also make these floats :3
|
||||||
pThisPad->m_iNormalizedLeftThumbY = pThisPad->m_iLeftThumbY / m_iEffectiveRange;
|
pThisPad->m_fNormalizedLeftThumbX = (float)pThisPad->m_iLeftThumbX / m_fEffectiveRange;
|
||||||
pThisPad->m_iNormalizedRightThumbX = pThisPad->m_iRightThumbX / m_iEffectiveRange;
|
pThisPad->m_fNormalizedLeftThumbY = (float)pThisPad->m_iLeftThumbY / m_fEffectiveRange;
|
||||||
pThisPad->m_iNormalizedRightThumbY = pThisPad->m_iRightThumbY / m_iEffectiveRange;
|
pThisPad->m_fNormalizedRightThumbX = (float)pThisPad->m_iRightThumbX / m_fEffectiveRange;
|
||||||
|
pThisPad->m_fNormalizedRightThumbY = (float)pThisPad->m_iRightThumbY / m_fEffectiveRange;
|
||||||
pThisPad->m_fNormalizedLeftThumbX = pThisPad->m_iLeftThumbX / m_fEffectiveRange;
|
|
||||||
pThisPad->m_fNormalizedLeftThumbY = pThisPad->m_iLeftThumbY / m_fEffectiveRange;
|
|
||||||
pThisPad->m_fNormalizedRightThumbX = pThisPad->m_iRightThumbX / m_fEffectiveRange;
|
|
||||||
pThisPad->m_fNormalizedRightThumbY = pThisPad->m_iRightThumbY / m_fEffectiveRange;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CInput::SetDeadzoneAndMovementRange(unsigned int uiDeadzone, unsigned int uiMovementRangeMax)
|
void CInput::SetDeadzoneAndMovementRange(unsigned int uiDeadzone, unsigned int uiMovementRangeMax)
|
||||||
@@ -317,6 +312,16 @@ unsigned int CInput::GetGameJoypadMaps(unsigned char ucMap, unsigned char ucActi
|
|||||||
return m_JoypadMap[ucMap][ucAction];
|
return m_JoypadMap[ucMap][ucAction];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* CInput::GetGamepadType(int iPad)
|
||||||
|
{
|
||||||
|
if (!m_Joypads[iPad].m_pGamepad)
|
||||||
|
{
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
return GamepadTypeName(SDL_GetGamepadType(m_Joypads[iPad].m_pGamepad));
|
||||||
|
}
|
||||||
|
|
||||||
void CInput::SetJoypadMapVal(int iPad, unsigned char ucMap)
|
void CInput::SetJoypadMapVal(int iPad, unsigned char ucMap)
|
||||||
{
|
{
|
||||||
m_bJoypadMapArrayIsSetup = true;
|
m_bJoypadMapArrayIsSetup = true;
|
||||||
@@ -615,21 +620,25 @@ FLOAT CInput::GetIdleSeconds(int iPad)
|
|||||||
|
|
||||||
void CInput::HandleGamepadEvents()
|
void CInput::HandleGamepadEvents()
|
||||||
{
|
{
|
||||||
while (SDL_PollEvent(&event))
|
//str1k3r - writing/reading a global can make stuff stutter alot
|
||||||
|
SDL_Event localEvent;
|
||||||
|
|
||||||
|
while (SDL_PollEvent(&localEvent))
|
||||||
{
|
{
|
||||||
switch (event.type)
|
switch (localEvent.type)
|
||||||
{
|
{
|
||||||
case SDL_EVENT_GAMEPAD_ADDED:
|
case SDL_EVENT_GAMEPAD_ADDED:
|
||||||
for (int i = 0; i < MAX_JOYPADS; i++)
|
for (int i = 0; i < MAX_JOYPADS; i++)
|
||||||
{
|
{
|
||||||
if (!m_Joypads[i].m_pGamepad)
|
if (!m_Joypads[i].m_pGamepad)
|
||||||
{
|
{
|
||||||
SDL_Gamepad *pad = SDL_OpenGamepad(event.gdevice.which);
|
SDL_Gamepad *pad = SDL_OpenGamepad(localEvent.gdevice.which);
|
||||||
|
|
||||||
if (pad)
|
if (pad)
|
||||||
{
|
{
|
||||||
SDL_SetGamepadPlayerIndex(pad, i);
|
SDL_SetGamepadPlayerIndex(pad, i);
|
||||||
m_Joypads[i].m_pGamepad = pad;
|
m_Joypads[i].m_pGamepad = pad;
|
||||||
|
m_Joypads[i].m_bIsConnected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -637,23 +646,73 @@ void CInput::HandleGamepadEvents()
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||||
for (int i = 0; i < MAX_JOYPADS; i++)
|
|
||||||
{
|
{
|
||||||
if (m_Joypads[i].m_pGamepad &&
|
int removedIndex = -1;
|
||||||
SDL_GetGamepadID(m_Joypads[i].m_pGamepad) == event.gdevice.which)
|
|
||||||
{
|
|
||||||
SDL_CloseGamepad(m_Joypads[i].m_pGamepad);
|
|
||||||
m_Joypads[i].m_pGamepad = nullptr;
|
|
||||||
m_Joypads[i].m_bIsConnected = false;
|
|
||||||
|
|
||||||
break;
|
for (int i = 0; i < MAX_JOYPADS; i++)
|
||||||
|
{
|
||||||
|
if (m_Joypads[i].m_pGamepad &&
|
||||||
|
SDL_GetGamepadID(m_Joypads[i].m_pGamepad) == localEvent.gdevice.which)
|
||||||
|
{
|
||||||
|
SDL_CloseGamepad(m_Joypads[i].m_pGamepad);
|
||||||
|
m_Joypads[i].m_pGamepad = nullptr;
|
||||||
|
m_Joypads[i].m_bIsConnected = false;
|
||||||
|
removedIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (removedIndex == 0)
|
||||||
|
{
|
||||||
|
for (int i = 1; i < MAX_JOYPADS; i++)
|
||||||
|
{
|
||||||
|
//str1k3r - since we used to never set m_bIsConnected when a gamepad was added this never ran
|
||||||
|
if (m_Joypads[i].m_bIsConnected && m_Joypads[i].m_pGamepad)
|
||||||
|
{
|
||||||
|
ShiftGamepadData(0, i);
|
||||||
|
SDL_SetGamepadPlayerIndex(m_Joypads[0].m_pGamepad, 0);
|
||||||
|
ClearGamepadSlot(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CInput::ShiftGamepadData(int destIndex, int srcIndex)
|
||||||
|
{
|
||||||
|
m_Joypads[destIndex] = m_Joypads[srcIndex];
|
||||||
|
|
||||||
|
m_Joypads[destIndex].m_pfLeftThumbXAxisMap = &m_Joypads[destIndex].m_fNormalizedLeftThumbX;
|
||||||
|
m_Joypads[destIndex].m_pfLeftThumbYAxisMap = &m_Joypads[destIndex].m_fNormalizedLeftThumbY;
|
||||||
|
m_Joypads[destIndex].m_pfRightThumbXAxisMap = &m_Joypads[destIndex].m_fNormalizedRightThumbX;
|
||||||
|
m_Joypads[destIndex].m_pfRightThumbYAxisMap = &m_Joypads[destIndex].m_fNormalizedRightThumbY;
|
||||||
|
m_Joypads[destIndex].m_pucLeftTriggerAxisMap = &m_Joypads[destIndex].m_ucLeftTriggerState;
|
||||||
|
m_Joypads[destIndex].m_pucRightTriggerAxisMap = &m_Joypads[destIndex].m_ucRightTriggerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInput::ClearGamepadSlot(int index)
|
||||||
|
{
|
||||||
|
m_Joypads[index].m_pGamepad = nullptr;
|
||||||
|
m_Joypads[index].m_bIsConnected = false;
|
||||||
|
m_Joypads[index].m_uiButtons = 0;
|
||||||
|
m_Joypads[index].m_ucInputStateIndex = -1;
|
||||||
|
m_Joypads[index].m_ucLeftTriggerState = 0;
|
||||||
|
m_Joypads[index].m_ucRightTriggerState = 0;
|
||||||
|
m_Joypads[index].m_iRightThumbX = 0;
|
||||||
|
m_Joypads[index].m_iRightThumbY = 0;
|
||||||
|
|
||||||
|
m_Joypads[index].m_pfLeftThumbXAxisMap = &m_Joypads[index].m_fNormalizedLeftThumbX;
|
||||||
|
m_Joypads[index].m_pfLeftThumbYAxisMap = &m_Joypads[index].m_fNormalizedLeftThumbY;
|
||||||
|
m_Joypads[index].m_pfRightThumbXAxisMap = &m_Joypads[index].m_fNormalizedRightThumbX;
|
||||||
|
m_Joypads[index].m_pfRightThumbYAxisMap = &m_Joypads[index].m_fNormalizedRightThumbY;
|
||||||
|
m_Joypads[index].m_pucLeftTriggerAxisMap = &m_Joypads[index].m_ucLeftTriggerState;
|
||||||
|
m_Joypads[index].m_pucRightTriggerAxisMap = &m_Joypads[index].m_ucRightTriggerState;
|
||||||
|
}
|
||||||
|
|
||||||
bool CInput::UpdateJoypads()
|
bool CInput::UpdateJoypads()
|
||||||
{
|
{
|
||||||
assert(m_bJoypadMapArrayIsSetup);
|
assert(m_bJoypadMapArrayIsSetup);
|
||||||
|
|||||||
@@ -31,6 +31,26 @@ SOFTWARE.
|
|||||||
|
|
||||||
class CInput
|
class CInput
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
const char* GamepadTypeName(SDL_GamepadType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case SDL_GAMEPAD_TYPE_UNKNOWN: return "UNKNOWN";
|
||||||
|
case SDL_GAMEPAD_TYPE_GAMECUBE: return "GAMECUBE";
|
||||||
|
case SDL_GAMEPAD_TYPE_STANDARD: return "STANDARD";
|
||||||
|
case SDL_GAMEPAD_TYPE_XBOX360: return "XBOX360";
|
||||||
|
case SDL_GAMEPAD_TYPE_XBOXONE: return "XBOXONE";
|
||||||
|
case SDL_GAMEPAD_TYPE_PS3: return "PS3";
|
||||||
|
case SDL_GAMEPAD_TYPE_PS4: return "PS4";
|
||||||
|
case SDL_GAMEPAD_TYPE_PS5: return "PS5";
|
||||||
|
case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: return "SWITCH_PRO";
|
||||||
|
case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: return "JOYCON_L";
|
||||||
|
case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: return "JOYCON_R";
|
||||||
|
case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: return "JOYCON_PAIR";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
public:
|
public:
|
||||||
struct JOYPAD
|
struct JOYPAD
|
||||||
{
|
{
|
||||||
@@ -101,6 +121,7 @@ public:
|
|||||||
|
|
||||||
void SetGameJoypadMaps(unsigned char ucMap, unsigned char ucAction, unsigned int uiActionVal);
|
void SetGameJoypadMaps(unsigned char ucMap, unsigned char ucAction, unsigned int uiActionVal);
|
||||||
unsigned int GetGameJoypadMaps(unsigned char ucMap, unsigned char ucAction);
|
unsigned int GetGameJoypadMaps(unsigned char ucMap, unsigned char ucAction);
|
||||||
|
const char* GetGamepadType(int iPad);
|
||||||
void SetJoypadMapVal(int iPad, unsigned char ucMap);
|
void SetJoypadMapVal(int iPad, unsigned char ucMap);
|
||||||
unsigned char GetJoypadMapVal(int iPad);
|
unsigned char GetJoypadMapVal(int iPad);
|
||||||
|
|
||||||
@@ -137,6 +158,8 @@ public:
|
|||||||
FLOAT GetIdleSeconds(int iPad);
|
FLOAT GetIdleSeconds(int iPad);
|
||||||
|
|
||||||
void HandleGamepadEvents();
|
void HandleGamepadEvents();
|
||||||
|
void ShiftGamepadData(int destIndex, int srcIndex);
|
||||||
|
void ClearGamepadSlot(int index);
|
||||||
bool UpdateJoypads(void);
|
bool UpdateJoypads(void);
|
||||||
void ClearJoypadValues(JOYPAD *pThisPad);
|
void ClearJoypadValues(JOYPAD *pThisPad);
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@
|
|||||||
<SDLCheck>true</SDLCheck>
|
<SDLCheck>true</SDLCheck>
|
||||||
<AdditionalIncludeDirectories>../Headers/Windows64;../Headers;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>../Headers/Windows64;../Headers;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
|
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
|
||||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||||
<MinimalRebuild>false</MinimalRebuild>
|
<MinimalRebuild>false</MinimalRebuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@@ -153,15 +153,16 @@
|
|||||||
<ClCompile>
|
<ClCompile>
|
||||||
<WarningLevel>Level3</WarningLevel>
|
<WarningLevel>Level3</WarningLevel>
|
||||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||||
<Optimization>MaxSpeed</Optimization>
|
<Optimization>Full</Optimization>
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
<IntrinsicFunctions>false</IntrinsicFunctions>
|
||||||
<PreprocessorDefinitions>_WINDOWS64;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>_WINDOWS64;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<SDLCheck>true</SDLCheck>
|
<SDLCheck>true</SDLCheck>
|
||||||
<AdditionalIncludeDirectories>../Headers/Windows64;../Headers;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>../Headers/Windows64;../Headers;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
|
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
|
||||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||||
|
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Windows</SubSystem>
|
<SubSystem>Windows</SubSystem>
|
||||||
|
|||||||
@@ -23,6 +23,46 @@ SOFTWARE.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "4J_Profile.h"
|
#include "4J_Profile.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
C_4JProfile ProfileManager;
|
C_4JProfile ProfileManager;
|
||||||
|
|
||||||
|
//str1k3r - GetSettings is basically snatched from smartCMD
|
||||||
|
void GetSettings(wchar_t *outPath, DWORD size)
|
||||||
|
{
|
||||||
|
GetModuleFileNameW(NULL, outPath, size); //str1k3r - unlike smartCMD we use wide so we can handle non english characters
|
||||||
|
wchar_t *lastSlash = wcsrchr(outPath, '\\');
|
||||||
|
if (lastSlash) *(lastSlash + 1) = '\0';
|
||||||
|
wcsncat_s(outPath, size, L"settings.dat", _TRUNCATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void C_4JProfile::LoadSettings(void* gs, size_t gsSize)
|
||||||
|
{
|
||||||
|
if (!gs || gsSize == 0) return;
|
||||||
|
|
||||||
|
wchar_t settingsPath[MAX_PATH] = {};
|
||||||
|
GetSettings(settingsPath, MAX_PATH);
|
||||||
|
|
||||||
|
FILE *file = NULL;
|
||||||
|
if (_wfopen_s(&file, settingsPath, L"rb") == 0 && file) //str1k3r - fun fact theres a version of fopen_s but for wide :3
|
||||||
|
{
|
||||||
|
fread(gs, 1, gsSize, file);
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void C_4JProfile::SaveSettings(void* gs, size_t gsSize)
|
||||||
|
{
|
||||||
|
if (!gs || gsSize == 0) return;
|
||||||
|
|
||||||
|
wchar_t settingsPath[MAX_PATH] = {};
|
||||||
|
GetSettings(settingsPath, MAX_PATH);
|
||||||
|
|
||||||
|
FILE *file = NULL;
|
||||||
|
if (_wfopen_s(&file, settingsPath, L"wb") == 0 && file)
|
||||||
|
{
|
||||||
|
fwrite(gs, 1, gsSize, file);
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -160,10 +160,6 @@
|
|||||||
<SubSystem>Windows</SubSystem>
|
<SubSystem>Windows</SubSystem>
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
</Link>
|
</Link>
|
||||||
<PreLinkEvent>
|
|
||||||
<Command>
|
|
||||||
</Command>
|
|
||||||
</PreLinkEvent>
|
|
||||||
<PreBuildEvent>
|
<PreBuildEvent>
|
||||||
<Command>./Windows64/shaders/build.bat</Command>
|
<Command>./Windows64/shaders/build.bat</Command>
|
||||||
</PreBuildEvent>
|
</PreBuildEvent>
|
||||||
@@ -191,9 +187,9 @@
|
|||||||
<ClCompile>
|
<ClCompile>
|
||||||
<WarningLevel>Level3</WarningLevel>
|
<WarningLevel>Level3</WarningLevel>
|
||||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||||
<Optimization>MaxSpeed</Optimization>
|
<Optimization>Full</Optimization>
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
<IntrinsicFunctions>false</IntrinsicFunctions>
|
||||||
<PreprocessorDefinitions>_WINDOWS64;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>_WINDOWS64;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<SDLCheck>true</SDLCheck>
|
<SDLCheck>true</SDLCheck>
|
||||||
<AdditionalIncludeDirectories>../Headers/Windows64;../Headers;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>../Headers/Windows64;../Headers;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
@@ -201,6 +197,7 @@
|
|||||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
<CompileAs>CompileAsCpp</CompileAs>
|
<CompileAs>CompileAsCpp</CompileAs>
|
||||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||||
|
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Windows</SubSystem>
|
<SubSystem>Windows</SubSystem>
|
||||||
@@ -208,6 +205,9 @@
|
|||||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
<OptimizeReferences>true</OptimizeReferences>
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
</Link>
|
</Link>
|
||||||
|
<PreBuildEvent>
|
||||||
|
<Command>./Windows64/shaders/build.bat</Command>
|
||||||
|
</PreBuildEvent>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ SOFTWARE.
|
|||||||
|
|
||||||
C4JRender RenderManager;
|
C4JRender RenderManager;
|
||||||
|
|
||||||
|
//str1k3r - set VSync to true by default
|
||||||
|
bool C4JRender::m_VSyncState = true;
|
||||||
|
|
||||||
void C4JRender::Tick()
|
void C4JRender::Tick()
|
||||||
{
|
{
|
||||||
InternalRenderManager.CBuffTick();
|
InternalRenderManager.CBuffTick();
|
||||||
@@ -38,6 +41,16 @@ void C4JRender::UpdateGamma(unsigned short usGamma)
|
|||||||
InternalRenderManager.UpdateGamma(usGamma);
|
InternalRenderManager.UpdateGamma(usGamma);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void C4JRender::SetVSync(bool vs)
|
||||||
|
{
|
||||||
|
m_VSyncState = vs;
|
||||||
|
}
|
||||||
|
|
||||||
|
int C4JRender::GetVSync()
|
||||||
|
{
|
||||||
|
return m_VSyncState ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
void C4JRender::MatrixMode(int type)
|
void C4JRender::MatrixMode(int type)
|
||||||
{
|
{
|
||||||
InternalRenderManager.MatrixMode(type);
|
InternalRenderManager.MatrixMode(type);
|
||||||
@@ -133,6 +146,11 @@ void C4JRender::SetClearColour(const float colourRGBA[4])
|
|||||||
InternalRenderManager.SetClearColour(colourRGBA);
|
InternalRenderManager.SetClearColour(colourRGBA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void C4JRender::setResolution(int ScreenWidth, int ScreenHeight)
|
||||||
|
{
|
||||||
|
InternalRenderManager.setResolution(ScreenWidth, ScreenHeight);
|
||||||
|
}
|
||||||
|
|
||||||
bool C4JRender::IsWidescreen()
|
bool C4JRender::IsWidescreen()
|
||||||
{
|
{
|
||||||
return InternalRenderManager.IsWidescreen();
|
return InternalRenderManager.IsWidescreen();
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public:
|
|||||||
void Present();
|
void Present();
|
||||||
void Clear(int flags, D3D11_RECT *pRect);
|
void Clear(int flags, D3D11_RECT *pRect);
|
||||||
void SetClearColour(const float colourRGBA[4]);
|
void SetClearColour(const float colourRGBA[4]);
|
||||||
|
void setResolution(int ScreenWidth, int ScreenHeight);
|
||||||
bool IsWidescreen();
|
bool IsWidescreen();
|
||||||
bool IsHiDef();
|
bool IsHiDef();
|
||||||
void CaptureThumbnail(ImageFileBuffer *pngOut);
|
void CaptureThumbnail(ImageFileBuffer *pngOut);
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ D3D11_PRIMITIVE_TOPOLOGY Renderer::g_topologies[C4JRender::PRIMITIVE_TYPE_COUNT]
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const unsigned int kVertexBufferSize = 0x100000;
|
static const unsigned int kVertexBufferSize = 0x100000;
|
||||||
static const unsigned int kScreenGrabWidth = 1920;
|
static unsigned int kScreenWidth = 1920;
|
||||||
static const unsigned int kScreenGrabHeight = 1080;
|
static unsigned int kScreenHeight = 1080;
|
||||||
static const unsigned int kThumbnailSize = 64;
|
static const unsigned int kThumbnailSize = 64;
|
||||||
|
|
||||||
static const unsigned int g_vertexStrides[C4JRender::VERTEX_TYPE_COUNT] = { 32, 16, 32, 32 };
|
static const unsigned int g_vertexStrides[C4JRender::VERTEX_TYPE_COUNT] = { 32, 16, 32, 32 };
|
||||||
@@ -854,14 +854,27 @@ ID3D11DeviceContext *Renderer::InitialiseContext(bool fromPresent)
|
|||||||
return deviceContext;
|
return deviceContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//str1k3r - yo fuck you 4J and your hardcoded ass game
|
||||||
|
void Renderer::setResolution(int ScreenWidth, int ScreenHeight)
|
||||||
|
{
|
||||||
|
if(kScreenHeight != ScreenHeight || kScreenWidth != ScreenWidth)
|
||||||
|
{
|
||||||
|
kScreenHeight = ScreenHeight;
|
||||||
|
kScreenWidth = ScreenWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Renderer::IsHiDef()
|
bool Renderer::IsHiDef()
|
||||||
{
|
{
|
||||||
return true;
|
return (kScreenHeight >= 720 && kScreenWidth >= 1280);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Renderer::IsWidescreen()
|
bool Renderer::IsWidescreen()
|
||||||
{
|
{
|
||||||
return true;
|
if (kScreenHeight == 0) return false;
|
||||||
|
|
||||||
|
float aspectRatio = static_cast<float>(kScreenWidth) / kScreenHeight;
|
||||||
|
return (aspectRatio >= 1.56f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::Present()
|
void Renderer::Present()
|
||||||
@@ -872,7 +885,7 @@ void Renderer::Present()
|
|||||||
{
|
{
|
||||||
PROFILER_SCOPE("Renderer::Present", "ScreenGrab", MP_MAGENTA);
|
PROFILER_SCOPE("Renderer::Present", "ScreenGrab", MP_MAGENTA);
|
||||||
|
|
||||||
unsigned char *linearData = new unsigned char[kScreenGrabWidth * kScreenGrabHeight * 4];
|
unsigned char *linearData = new unsigned char[kScreenWidth * kScreenHeight * 4];
|
||||||
|
|
||||||
ID3D11Resource *backBufferResource = NULL;
|
ID3D11Resource *backBufferResource = NULL;
|
||||||
ID3D11Texture2D *backBuffer = NULL;
|
ID3D11Texture2D *backBuffer = NULL;
|
||||||
@@ -898,13 +911,13 @@ void Renderer::Present()
|
|||||||
m_pDeviceContext->Map(stagingTexture, 0, D3D11_MAP_READ_WRITE, 0, &mapped);
|
m_pDeviceContext->Map(stagingTexture, 0, D3D11_MAP_READ_WRITE, 0, &mapped);
|
||||||
const unsigned char* src = reinterpret_cast<const unsigned char*>(mapped.pData);
|
const unsigned char* src = reinterpret_cast<const unsigned char*>(mapped.pData);
|
||||||
|
|
||||||
for (UINT y = 0; y < kScreenGrabHeight; ++y)
|
for (UINT y = 0; y < kScreenHeight; ++y)
|
||||||
{
|
{
|
||||||
unsigned char *dstRow = linearData + y * kScreenGrabWidth * 4;
|
unsigned char *dstRow = linearData + y * kScreenWidth * 4;
|
||||||
const unsigned char *srcRow = src + y * mapped.RowPitch;
|
const unsigned char *srcRow = src + y * mapped.RowPitch;
|
||||||
std::memcpy(dstRow, srcRow, kScreenGrabWidth * 4);
|
std::memcpy(dstRow, srcRow, kScreenWidth * 4);
|
||||||
|
|
||||||
for (UINT x = 0; x < kScreenGrabWidth; ++x)
|
for (UINT x = 0; x < kScreenWidth; ++x)
|
||||||
dstRow[x * 4 + 3] = 0xFF;
|
dstRow[x * 4 + 3] = 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,8 +939,8 @@ void Renderer::Present()
|
|||||||
UTCSysTime.wSecond);
|
UTCSysTime.wSecond);
|
||||||
|
|
||||||
D3DXIMAGE_INFO info;
|
D3DXIMAGE_INFO info;
|
||||||
info.Width = kScreenGrabWidth;
|
info.Width = kScreenWidth;
|
||||||
info.Height = kScreenGrabHeight;
|
info.Height = kScreenHeight;
|
||||||
SaveTextureData(fileName, &info, reinterpret_cast<int*>(linearData));
|
SaveTextureData(fileName, &info, reinterpret_cast<int*>(linearData));
|
||||||
|
|
||||||
delete[] linearData;
|
delete[] linearData;
|
||||||
@@ -935,7 +948,7 @@ void Renderer::Present()
|
|||||||
m_bShouldScreenGrabNextFrame = false;
|
m_bShouldScreenGrabNextFrame = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_pSwapChain->Present(1, 0);
|
m_pSwapChain->Present(C4JRender::GetVSync(), 0);
|
||||||
++presentCount;
|
++presentCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1046,4 +1059,3 @@ Renderer::Context &Renderer::getContext()
|
|||||||
{
|
{
|
||||||
return *reinterpret_cast<Renderer::Context*>(TlsGetValue(Renderer::tlsIdx));
|
return *reinterpret_cast<Renderer::Context*>(TlsGetValue(Renderer::tlsIdx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,15 +149,16 @@
|
|||||||
<ClCompile>
|
<ClCompile>
|
||||||
<WarningLevel>Level3</WarningLevel>
|
<WarningLevel>Level3</WarningLevel>
|
||||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||||
<Optimization>MaxSpeed</Optimization>
|
<Optimization>Full</Optimization>
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
<IntrinsicFunctions>false</IntrinsicFunctions>
|
||||||
<PreprocessorDefinitions>_WINDOWS64;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>_WINDOWS64;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<SDLCheck>true</SDLCheck>
|
<SDLCheck>true</SDLCheck>
|
||||||
<AdditionalIncludeDirectories>../Headers/Windows64;../Headers;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>../Headers/Windows64;../Headers;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
|
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
|
||||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||||
|
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Windows</SubSystem>
|
<SubSystem>Windows</SubSystem>
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
<SDLCheck>true</SDLCheck>
|
<SDLCheck>true</SDLCheck>
|
||||||
<AdditionalIncludeDirectories>../Headers;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>../Headers;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
|
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
|
||||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Windows</SubSystem>
|
<SubSystem>Windows</SubSystem>
|
||||||
@@ -126,14 +126,15 @@
|
|||||||
<ClCompile>
|
<ClCompile>
|
||||||
<WarningLevel>Level3</WarningLevel>
|
<WarningLevel>Level3</WarningLevel>
|
||||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||||
<Optimization>MaxSpeed</Optimization>
|
<Optimization>Full</Optimization>
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
<IntrinsicFunctions>false</IntrinsicFunctions>
|
||||||
<PreprocessorDefinitions>_WINDOWS64;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
|
<PreprocessorDefinitions>_WINDOWS64;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
|
||||||
<SDLCheck>true</SDLCheck>
|
<SDLCheck>true</SDLCheck>
|
||||||
<AdditionalIncludeDirectories>../Headers;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>../Headers;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
|
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
|
||||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
|
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Windows</SubSystem>
|
<SubSystem>Windows</SubSystem>
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ public:
|
|||||||
EKeyboardResult RequestKeyboard(LPCWSTR Title, LPCWSTR Text, DWORD dwPad, UINT uiMaxChars, int( *Func)(LPVOID,const bool),LPVOID lpParam,C_4JInput::EKeyboardMode eMode);
|
EKeyboardResult RequestKeyboard(LPCWSTR Title, LPCWSTR Text, DWORD dwPad, UINT uiMaxChars, int( *Func)(LPVOID,const bool),LPVOID lpParam,C_4JInput::EKeyboardMode eMode);
|
||||||
void DestroyKeyboard();
|
void DestroyKeyboard();
|
||||||
void GetText(uint16_t *UTF16String);
|
void GetText(uint16_t *UTF16String);
|
||||||
|
const char* PollControllerType(int ipad);
|
||||||
|
|
||||||
void SetLostInputControlCallback(void( *Func)(LPVOID, bool),LPVOID lpParam);
|
void SetLostInputControlCallback(void( *Func)(LPVOID, bool),LPVOID lpParam);
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ public:
|
|||||||
void ResetProfileProcessState(); // after a sign out from the primary player, call this
|
void ResetProfileProcessState(); // after a sign out from the primary player, call this
|
||||||
void Tick( void );
|
void Tick( void );
|
||||||
|
|
||||||
|
// SETTINGS
|
||||||
|
void LoadSettings(void* gs, size_t gsSize);
|
||||||
|
void SaveSettings(void* gs, size_t gsSize);
|
||||||
|
|
||||||
// ACHIEVEMENTS & AWARDS
|
// ACHIEVEMENTS & AWARDS
|
||||||
|
|
||||||
void RegisterAward(int iAwardNumber,int iGamerconfigID, eAwardType eType, bool bLeaderboardAffected=false,
|
void RegisterAward(int iAwardNumber,int iGamerconfigID, eAwardType eType, bool bLeaderboardAffected=false,
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ public:
|
|||||||
void Tick();
|
void Tick();
|
||||||
void UpdateGamma(unsigned short usGamma);
|
void UpdateGamma(unsigned short usGamma);
|
||||||
|
|
||||||
|
// VSync
|
||||||
|
static bool m_VSyncState;
|
||||||
|
static void SetVSync(bool vs);
|
||||||
|
static int GetVSync();
|
||||||
|
|
||||||
// Matrix stack
|
// Matrix stack
|
||||||
void MatrixMode(int type);
|
void MatrixMode(int type);
|
||||||
void MatrixSetIdentity();
|
void MatrixSetIdentity();
|
||||||
@@ -79,6 +84,7 @@ public:
|
|||||||
void Present();
|
void Present();
|
||||||
void Clear(int flags, D3D11_RECT *pRect = NULL);
|
void Clear(int flags, D3D11_RECT *pRect = NULL);
|
||||||
void SetClearColour(const float colourRGBA[4]);
|
void SetClearColour(const float colourRGBA[4]);
|
||||||
|
void setResolution(int ScreenWidth, int ScreenHeight);
|
||||||
bool IsWidescreen();
|
bool IsWidescreen();
|
||||||
bool IsHiDef();
|
bool IsHiDef();
|
||||||
void CaptureThumbnail(ImageFileBuffer *pngOut);
|
void CaptureThumbnail(ImageFileBuffer *pngOut);
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# 4JLibs-VS2012
|
# 4JLibs-VS2012
|
||||||
|
|
||||||
|
## WARNING
|
||||||
|
This needs the [Windows 10 SDK](https://learn.microsoft.com/en-us/windows/apps/windows-sdk/downloads#windows-10) to build `4J_Render`.
|
||||||
|
|
||||||
## Implementation
|
## Implementation
|
||||||
|
|
||||||
For implementation details, please look at the [IMPLEMENTATION_GUIDE.md](.gitea/IMPLEMENTATION_GUIDE.md) file
|
For implementation details, please look at the [IMPLEMENTATION_GUIDE.md](.gitea/IMPLEMENTATION_GUIDE.md) file
|
||||||
|
|||||||
Reference in New Issue
Block a user