Initial TaikoSwitchDataTableDecryptor CLI program implementation
This commit is contained in:
parent
ef7033973a
commit
1ed1a17d9a
36
TaikoSwitchDataTableDecryptor.sln
Normal file
36
TaikoSwitchDataTableDecryptor.sln
Normal 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
|
@ -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>
|
@ -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>
|
107
TaikoSwitchDataTableDecryptor/src/EntryPoint.cpp
Normal file
107
TaikoSwitchDataTableDecryptor/src/EntryPoint.cpp
Normal 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();
|
||||
}
|
29
TaikoSwitchDataTableDecryptor/src/Types.h
Normal file
29
TaikoSwitchDataTableDecryptor/src/Types.h
Normal 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;
|
||||
};
|
317
TaikoSwitchDataTableDecryptor/src/Utilities.cpp
Normal file
317
TaikoSwitchDataTableDecryptor/src/Utilities.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
64
TaikoSwitchDataTableDecryptor/src/Utilities.h
Normal file
64
TaikoSwitchDataTableDecryptor/src/Utilities.h
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user