1
0
mirror of synced 2024-11-23 22:40:58 +01:00

Initial TaikoSwitchDataTableDecryptor CLI program implementation

This commit is contained in:
samyuu 2021-05-27 05:39:00 +02:00
parent ef7033973a
commit 1ed1a17d9a
7 changed files with 688 additions and 0 deletions

View File

@ -0,0 +1,36 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.1525
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TaikoSwitchDataTableDecryptor", "TaikoSwitchDataTableDecryptor\TaikoSwitchDataTableDecryptor.vcxproj", "{2A0F9E61-C7B2-497B-B587-C580E0C17110}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "Dependencies\zlib\zlib.vcxproj", "{164A9D43-345C-407B-BF59-527386DED788}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{EE11261E-85ED-4445-AB6F-5C4F55B4E34B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2A0F9E61-C7B2-497B-B587-C580E0C17110}.Debug|x64.ActiveCfg = Debug|x64
{2A0F9E61-C7B2-497B-B587-C580E0C17110}.Debug|x64.Build.0 = Debug|x64
{2A0F9E61-C7B2-497B-B587-C580E0C17110}.Release|x64.ActiveCfg = Release|x64
{2A0F9E61-C7B2-497B-B587-C580E0C17110}.Release|x64.Build.0 = Release|x64
{164A9D43-345C-407B-BF59-527386DED788}.Debug|x64.ActiveCfg = Debug|x64
{164A9D43-345C-407B-BF59-527386DED788}.Debug|x64.Build.0 = Debug|x64
{164A9D43-345C-407B-BF59-527386DED788}.Release|x64.ActiveCfg = Release|x64
{164A9D43-345C-407B-BF59-527386DED788}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{164A9D43-345C-407B-BF59-527386DED788} = {EE11261E-85ED-4445-AB6F-5C4F55B4E34B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {32848A13-A06E-49EA-B024-22472419BF2F}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{2A0F9E61-C7B2-497B-B587-C580E0C17110}</ProjectGuid>
<RootNamespace>TaikoSwitchDataTableDecryptor</RootNamespace>
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(ProjectDir)bin\$(Platform)-$(Configuration)\</OutDir>
<IntDir>$(ProjectDir)bin-int\$(Platform)-$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(ProjectDir)bin\$(Platform)-$(Configuration)\</OutDir>
<IntDir>$(ProjectDir)bin-int\$(Platform)-$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<AdditionalIncludeDirectories>$(ProjectDir)src;$(SolutionDir)Dependencies\zlib\include</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>zlib.lib;Bcrypt.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(SolutionDir)Dependencies\zlib\bin\$(Platform)-$(Configuration)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<AdditionalIncludeDirectories>$(ProjectDir)src;$(SolutionDir)Dependencies\zlib\include</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>zlib.lib;Bcrypt.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(SolutionDir)Dependencies\zlib\bin\$(Platform)-$(Configuration)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\EntryPoint.cpp" />
<ClCompile Include="src\Utilities.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\Types.h" />
<ClInclude Include="src\Utilities.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

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

View File

@ -0,0 +1,107 @@
#include "Types.h"
#include "Utilities.h"
namespace PeepoHappy
{
bool HasValidGZipHeader(const u8* fileContent, size_t fileSize)
{
if (fileSize <= Crypto::AesKeySize)
return false;
constexpr std::array<u8, 2> gzibMagic = { 0x1F, 0x8B };
const bool validMagic = (memcmp(fileContent, gzibMagic.data(), gzibMagic.size()) == 0);
const bool validCompressionMethod = (fileContent[2] == /*Z_DEFLATED*/ 0x08);
return (validMagic && validCompressionMethod);
}
bool DecompressAndWriteDataTableJsonFile(const u8* compressedData, size_t compressedDataSize, std::string_view jsonOutputFilePath)
{
auto decompressedBuffer = std::make_unique<u8[]>(IO::MaxDecompressedGameDataTableFileSize);
if (Compression::Inflate(compressedData, compressedDataSize, decompressedBuffer.get(), IO::MaxDecompressedGameDataTableFileSize))
{
const size_t jsonLength = strnlen(reinterpret_cast<const char*>(decompressedBuffer.get()), IO::MaxDecompressedGameDataTableFileSize);
const auto jsonString = std::string_view(reinterpret_cast<const char*>(decompressedBuffer.get()), jsonLength);
if (IO::WriteEntireFile(jsonOutputFilePath, reinterpret_cast<const u8*>(jsonString.data()), jsonString.size()))
return true;
}
return false;
}
int ReadAndWriteEncryptedAndOrCompressedBinToJsonFile(std::string_view encryptedAndOrCompressedInputFilePath, std::string_view jsonOutputFilePath)
{
const auto[fileContent, fileSize] = IO::ReadEntireFile(encryptedAndOrCompressedInputFilePath);
if (fileSize <= 8)
{
fprintf(stderr, "Bad file? :WidePeepoSad:");
return EXIT_WIDEPEEPOSAD;
}
if (HasValidGZipHeader(fileContent.get(), fileSize))
{
if (!DecompressAndWriteDataTableJsonFile(fileContent.get(), fileSize, jsonOutputFilePath))
return EXIT_WIDEPEEPOSAD;
}
else
{
std::array<u8, Crypto::AesKeySize> iv = {};
memcpy(iv.data(), fileContent.get(), iv.size());
const size_t fileSizeWithoutIV = (fileSize - iv.size());
const u8* fileContentWithoutIV = (fileContent.get() + iv.size());
auto decryptedBuffer = std::make_unique<u8[]>(fileSizeWithoutIV);
Crypto::DecryptAes128Cbc(fileContentWithoutIV, decryptedBuffer.get(), fileSizeWithoutIV, Crypto::DataTableAesKey, iv);
if (!DecompressAndWriteDataTableJsonFile(decryptedBuffer.get(), fileSizeWithoutIV, jsonOutputFilePath))
return EXIT_WIDEPEEPOSAD;
}
return EXIT_WIDEPEEPOHAPPY;
}
int ReadAndWriteJsonFileToCompressedBin(std::string_view jsonInputFilePath, std::string_view compressedOutputFilePath)
{
auto compressedBuffer = std::make_unique<u8[]>(IO::MaxDecompressedGameDataTableFileSize);
const auto[fileContent, fileSize] = IO::ReadEntireFile(jsonInputFilePath);
const auto compressedSize = Compression::Deflate(fileContent.get(), fileSize, compressedBuffer.get(), IO::MaxDecompressedGameDataTableFileSize);
if (compressedSize < 0)
return EXIT_WIDEPEEPOSAD;
if (!IO::WriteEntireFile(compressedOutputFilePath, compressedBuffer.get(), compressedSize))
return EXIT_WIDEPEEPOSAD;
return EXIT_WIDEPEEPOHAPPY;
}
int EntryPoint()
{
const auto[argc, argv] = UTF8::GetCommandLineArguments();
if (argc <= 1)
{
fprintf(stderr, "Insufficient arguments :WidePeepoSad:\n");
return EXIT_WIDEPEEPOSAD;
}
const auto inputPath = std::string_view(argv[1]);
if (IO::HasFileExtension(inputPath, ".bin"))
return ReadAndWriteEncryptedAndOrCompressedBinToJsonFile(inputPath, IO::ChangeFileExtension(inputPath, ".json"));
if (IO::HasFileExtension(inputPath, ".json"))
return ReadAndWriteJsonFileToCompressedBin(inputPath, IO::ChangeFileExtension(inputPath, ".bin"));
fprintf(stderr, "Unknown file extension\n");
return EXIT_WIDEPEEPOSAD;
}
}
int main()
{
return PeepoHappy::EntryPoint();
}

View File

@ -0,0 +1,29 @@
#pragma once
#include <stdint.h>
#include <string>
#include <string_view>
#include <array>
#include <vector>
#include <memory>
#include <tuple>
#include <assert.h>
using i8 = int8_t;
using u8 = uint8_t;
using i16 = int16_t;
using u16 = uint16_t;
using i32 = int32_t;
using u32 = uint32_t;
using i64 = int64_t;
using u64 = uint64_t;
using f32 = float;
using f64 = double;
struct NonCopyable
{
NonCopyable() = default;
~NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};

View File

@ -0,0 +1,317 @@
#include "Utilities.h"
#include <zlib.h>
#define NOMINMAX
#include <Windows.h>
#include <bcrypt.h>
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((::NTSTATUS)(Status)) >= 0)
#endif // ! NT_SUCCESS
namespace PeepoHappy
{
namespace UTF8
{
std::string Narrow(std::wstring_view inputString)
{
std::string utf8String;
const int utf8Length = ::WideCharToMultiByte(CP_UTF8, 0, inputString.data(), static_cast<int>(inputString.size() + 1), nullptr, 0, nullptr, nullptr) - 1;
if (utf8Length > 0)
{
utf8String.resize(utf8Length);
::WideCharToMultiByte(CP_UTF8, 0, inputString.data(), static_cast<int>(inputString.size()), utf8String.data(), utf8Length, nullptr, nullptr);
}
return utf8String;
}
std::wstring Widen(std::string_view inputString)
{
std::wstring utf16String;
const int utf16Length = ::MultiByteToWideChar(CP_UTF8, 0, inputString.data(), static_cast<int>(inputString.size() + 1), nullptr, 0) - 1;
if (utf16Length > 0)
{
utf16String.resize(utf16Length);
::MultiByteToWideChar(CP_UTF8, 0, inputString.data(), static_cast<int>(inputString.size()), utf16String.data(), utf16Length);
}
return utf16String;
}
std::pair<int, const char**> GetCommandLineArguments()
{
static std::vector<std::string> argvString;
static std::vector<const char*> argvCStr;
if (!argvString.empty() || !argvCStr.empty())
return { static_cast<int>(argvString.size()), argvCStr.data() };
int argc = 0;
auto argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
argvString.reserve(argc);
argvCStr.reserve(argc);
for (auto i = 0; i < argc; i++)
argvCStr.emplace_back(argvString.emplace_back(UTF8::Narrow(argv[i])).c_str());
::LocalFree(argv);
return { argc, argvCStr.data() };
}
std::vector<std::string> GetArgV()
{
return std::vector<std::string>();
}
WideArg::WideArg(std::string_view inputString)
{
// NOTE: Length **without** null terminator
convertedLength = ::MultiByteToWideChar(CP_UTF8, 0, inputString.data(), static_cast<int>(inputString.size() + 1), nullptr, 0) - 1;
if (convertedLength <= 0)
{
stackBuffer[0] = L'\0';
return;
}
if (convertedLength < stackBuffer.size())
{
::MultiByteToWideChar(CP_UTF8, 0, inputString.data(), static_cast<int>(inputString.size()), stackBuffer.data(), convertedLength);
stackBuffer[convertedLength] = L'\0';
}
else
{
heapBuffer = std::make_unique<wchar_t[]>(convertedLength + 1);
::MultiByteToWideChar(CP_UTF8, 0, inputString.data(), static_cast<int>(inputString.size()), heapBuffer.get(), convertedLength);
heapBuffer[convertedLength] = L'\0';
}
}
const wchar_t* WideArg::c_str() const
{
return (convertedLength < stackBuffer.size()) ? stackBuffer.data() : heapBuffer.get();
}
}
namespace IO
{
std::pair<std::unique_ptr<u8[]>, size_t> ReadEntireFile(std::string_view filePath)
{
std::unique_ptr<u8[]> fileContent = nullptr;
size_t fileSize = 0;
::HANDLE fileHandle = ::CreateFileW(UTF8::WideArg(filePath).c_str(), GENERIC_READ, (FILE_SHARE_READ | FILE_SHARE_WRITE), NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (fileHandle != INVALID_HANDLE_VALUE)
{
::LARGE_INTEGER largeIntegerFileSize = {};
::GetFileSizeEx(fileHandle, &largeIntegerFileSize);
if (fileSize = static_cast<size_t>(largeIntegerFileSize.QuadPart); fileSize > 0)
{
if (fileContent = std::make_unique<u8[]>(fileSize); fileContent != nullptr)
{
assert(fileSize < std::numeric_limits<DWORD>::max() && "No way that's ever gonna happen, right?");
DWORD bytesRead = 0;
::ReadFile(fileHandle, fileContent.get(), static_cast<DWORD>(fileSize), &bytesRead, nullptr);
}
}
::CloseHandle(fileHandle);
}
return { std::move(fileContent), fileSize };
}
bool WriteEntireFile(std::string_view filePath, const u8* fileContent, size_t fileSize)
{
if (filePath.empty() || fileContent == nullptr || fileSize == 0)
return false;
::HANDLE fileHandle = ::CreateFileW(UTF8::WideArg(filePath).c_str(), GENERIC_WRITE, (FILE_SHARE_READ | FILE_SHARE_WRITE), NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (fileHandle == INVALID_HANDLE_VALUE)
return false;
assert(fileSize < std::numeric_limits<DWORD>::max() && "No way that's ever gonna happen, right?");
DWORD bytesWritten = 0;
::WriteFile(fileHandle, fileContent, static_cast<DWORD>(fileSize), &bytesWritten, nullptr);
::CloseHandle(fileHandle);
return true;
}
bool HasFileExtension(std::string_view filePath, std::string_view extensionToCheckFor)
{
assert(!extensionToCheckFor.empty() && extensionToCheckFor[0] == '.');
if (extensionToCheckFor.size() >= filePath.size())
return false;
const auto stringA = filePath.substr(filePath.size() - extensionToCheckFor.size());
const auto stringB = extensionToCheckFor;
return std::equal(stringA.begin(), stringA.end(), stringB.begin(), stringB.end(), [](char a, char b) { return ::tolower(a) == ::tolower(b); });
}
std::string ChangeFileExtension(std::string_view filePath, std::string_view newExtension)
{
const size_t lastSeparator = filePath.find_last_of("./\\");
if (lastSeparator != std::string_view::npos)
{
if (filePath[lastSeparator] == '.')
return std::string(filePath.substr(0, lastSeparator)) + std::string(newExtension);
}
return std::string(filePath) + std::string(newExtension);
}
}
namespace Crypto
{
bool DecryptAes128Cbc(const u8* inEncryptedData, u8* outDecryptedData, size_t inOutDataSize, std::array<u8, AesKeySize> key, std::array<u8, AesKeySize> iv)
{
bool successful = false;
::NTSTATUS status = {};
::BCRYPT_ALG_HANDLE algorithmHandle = {};
status = ::BCryptOpenAlgorithmProvider(&algorithmHandle, BCRYPT_AES_ALGORITHM, nullptr, 0);
if (NT_SUCCESS(status))
{
status = ::BCryptSetProperty(algorithmHandle, BCRYPT_CHAINING_MODE, reinterpret_cast<PBYTE>(const_cast<wchar_t*>(BCRYPT_CHAIN_MODE_CBC)), sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
if (NT_SUCCESS(status))
{
ULONG keyObjectSize = {};
ULONG copiedDataSize = {};
status = ::BCryptGetProperty(algorithmHandle, BCRYPT_OBJECT_LENGTH, reinterpret_cast<PBYTE>(&keyObjectSize), sizeof(ULONG), &copiedDataSize, 0);
if (NT_SUCCESS(status))
{
::BCRYPT_KEY_HANDLE symmetricKeyHandle = {};
auto keyObject = std::make_unique<u8[]>(keyObjectSize);
status = ::BCryptGenerateSymmetricKey(algorithmHandle, &symmetricKeyHandle, keyObject.get(), keyObjectSize, key.data(), static_cast<ULONG>(key.size()), 0);
if (NT_SUCCESS(status))
{
status = ::BCryptDecrypt(symmetricKeyHandle, const_cast<u8*>(inEncryptedData), static_cast<ULONG>(inOutDataSize), nullptr, iv.data(), static_cast<ULONG>(iv.size()), outDecryptedData, static_cast<ULONG>(inOutDataSize), &copiedDataSize, 0);
if (NT_SUCCESS(status))
{
successful = true;
}
else
{
fprintf(stderr, __FUNCTION__"(): BCryptDecrypt() failed with 0x%X", status);
}
if (symmetricKeyHandle)
::BCryptDestroyKey(symmetricKeyHandle);
}
else
{
fprintf(stderr, __FUNCTION__"(): BCryptGenerateSymmetricKey() failed with 0x%X", status);
}
}
else
{
fprintf(stderr, __FUNCTION__"(): BCryptGetProperty(BCRYPT_OBJECT_LENGTH) failed with 0x%X", status);
}
}
else
{
fprintf(stderr, __FUNCTION__"(): BCryptSetProperty(BCRYPT_CHAINING_MODE) failed with 0x%X", status);
}
if (algorithmHandle)
::BCryptCloseAlgorithmProvider(algorithmHandle, 0);
}
else
{
fprintf(stderr, __FUNCTION__"(): BCryptOpenAlgorithmProvider(BCRYPT_AES_ALGORITHM) failed with 0x%X", status);
}
return successful;
}
}
namespace Compression
{
bool Inflate(const u8* inCompressedData, size_t inDataSize, u8* outDecompressedData, size_t outDataSize)
{
z_stream zStream = {};
zStream.zalloc = Z_NULL;
zStream.zfree = Z_NULL;
zStream.opaque = Z_NULL;
zStream.avail_in = static_cast<uInt>(inDataSize);
zStream.next_in = static_cast<const Bytef*>(inCompressedData);
zStream.avail_out = static_cast<uInt>(outDataSize);
zStream.next_out = static_cast<Bytef*>(outDecompressedData);
const int initResult = inflateInit2(&zStream, 31);
if (initResult != Z_OK)
return false;
const int inflateResult = inflate(&zStream, Z_FINISH);
// assert(inflateResult == Z_STREAM_END && zStream.msg == nullptr);
const int endResult = inflateEnd(&zStream);
if (endResult != Z_OK)
return false;
return true;
}
size_t Deflate(const u8* inData, size_t inDataSize, u8* outCompressedData, size_t outDataSize)
{
constexpr size_t chunkStepSize = 0x4000;
z_stream zStream = {};
zStream.zalloc = Z_NULL;
zStream.zfree = Z_NULL;
zStream.opaque = Z_NULL;
int errorCode = deflateInit2(&zStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY);
assert(errorCode == Z_OK);
const u8* inDataReadHeader = static_cast<const u8*>(inData);
size_t remainingSize = inDataSize;
size_t compressedSize = 0;
while (remainingSize > 0)
{
const size_t chunkSize = std::min(remainingSize, chunkStepSize);
zStream.avail_in = static_cast<uInt>(chunkSize);
zStream.next_in = reinterpret_cast<const Bytef*>(inDataReadHeader);
inDataReadHeader += chunkSize;
remainingSize -= chunkSize;
do
{
std::array<u8, chunkStepSize> outputBuffer;
zStream.avail_out = chunkStepSize;
zStream.next_out = outputBuffer.data();
errorCode = deflate(&zStream, remainingSize == 0 ? Z_FINISH : Z_NO_FLUSH);
assert(errorCode != Z_STREAM_ERROR);
const auto compressedChunkSize = chunkStepSize - zStream.avail_out;
memcpy(&outCompressedData[compressedSize], outputBuffer.data(), compressedChunkSize);
compressedSize += compressedChunkSize;
}
while (zStream.avail_out == 0);
assert(zStream.avail_in == 0);
}
deflateEnd(&zStream);
assert(errorCode == Z_STREAM_END);
return compressedSize;
}
}
}

View File

@ -0,0 +1,64 @@
#pragma once
#include "Types.h"
#define EXIT_WIDEPEEPOHAPPY EXIT_SUCCESS
#define EXIT_WIDEPEEPOSAD EXIT_FAILURE
namespace PeepoHappy
{
// NOTE: Following the "UTF-8 Everywhere" guidelines
namespace UTF8
{
// NOTE: Convert UTF-16 to UTF-8
std::string Narrow(std::wstring_view);
// NOTE: Convert UTF-8 to UTF-16
std::wstring Widen(std::string_view);
// NOTE: To avoid needless heap allocations for temporary wchar_t C-API function arguments
// Example: DummyU16FuncW(UTF8::WideArg(stringU8).c_str(), ...)
class WideArg : NonCopyable
{
public:
WideArg(std::string_view);
const wchar_t* c_str() const;
private:
std::unique_ptr<wchar_t[]> heapBuffer;
std::array<wchar_t, 260> stackBuffer;
int convertedLength;
};
// NOTE: Includes the program file path as first argument
std::pair<int, const char**> GetCommandLineArguments();
}
namespace IO
{
// NOTE: Sucks for modders, makes sense for them to do it though...
constexpr size_t MaxDecompressedGameDataTableFileSize = 0x200000;
std::pair<std::unique_ptr<u8[]>, size_t> ReadEntireFile(std::string_view filePath);
bool WriteEntireFile(std::string_view filePath, const u8* fileContent, size_t fileSize);
bool HasFileExtension(std::string_view filePath, std::string_view extensionToCheckFor);
std::string ChangeFileExtension(std::string_view filePath, std::string_view newExtension);
}
namespace Crypto
{
constexpr size_t AesKeySize = 16;
// NOTE: Literally loaded directly into X8 right before calling nn::crypto::DecryptAes128Cbc()
// they couldn't even bother trying to "hide" it by adding a few pointer indirection or scrambling first :KEKL:
constexpr std::array<u8, AesKeySize> DataTableAesKey = { 0x57, 0x39, 0x73, 0x35, 0x38, 0x73, 0x68, 0x43, 0x54, 0x70, 0x76, 0x75, 0x6A, 0x6B, 0x4A, 0x74, };
bool DecryptAes128Cbc(const u8* inEncryptedData, u8* outDecryptedData, size_t inOutDataSize, std::array<u8, AesKeySize> key, std::array<u8, AesKeySize> iv);
}
namespace Compression
{
bool Inflate(const u8* inCompressedData, size_t inDataSize, u8* outDecompressedData, size_t outDataSize);
size_t Deflate(const u8* inData, size_t inDataSize, u8* outCompressedData, size_t outDataSize);
}
}