371 lines
12 KiB
C++
371 lines
12 KiB
C++
#include "pch.h"
|
||
#include "Utils.h"
|
||
#include "aes.h"
|
||
|
||
const uint8_t IV[] = { 0x72, 0xE0, 0x67, 0xFB, 0xDD, 0xCB, 0xCF, 0x77, 0xEB, 0xE8, 0xBC, 0x64, 0x3F, 0x63, 0x0D, 0x93 };
|
||
|
||
bool Utils::Detour32(char* src, char* dst, const intptr_t len)
|
||
{
|
||
if (len < 5) return false;
|
||
|
||
DWORD curProtection;
|
||
VirtualProtect(src, len, PAGE_EXECUTE_READWRITE, &curProtection);
|
||
|
||
intptr_t relativeAddress = (intptr_t)(dst - (intptr_t)src) - 5;
|
||
|
||
*src = (char)'\xE9';
|
||
*(intptr_t*)((intptr_t)src + 1) = relativeAddress;
|
||
|
||
VirtualProtect(src, len, curProtection, &curProtection);
|
||
return true;
|
||
}
|
||
|
||
char* Utils::TrampHook32(char* src, char* dst, const intptr_t len)
|
||
{
|
||
// Make sure the length is greater than 5
|
||
if (len < 5) return 0;
|
||
|
||
// Create the gateway (len + 5 for the overwritten bytes + the jmp)
|
||
void* gateway = VirtualAlloc(0, len + 5, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||
|
||
if (gateway == NULL) return 0;
|
||
|
||
// Write the stolen bytes into the gateway
|
||
memcpy(gateway, src, len);
|
||
|
||
// Get the gateway to destination addy
|
||
intptr_t gatewayRelativeAddr = ((intptr_t)src - (intptr_t)gateway) - 5;
|
||
|
||
// Add the jmp opcode to the end of the gateway
|
||
*(char*)((intptr_t)gateway + len) = 0xE9;
|
||
|
||
// Add the address to the jmp
|
||
*(intptr_t*)((intptr_t)gateway + len + 1) = gatewayRelativeAddr;
|
||
|
||
// Perform the detour
|
||
Detour32(src, dst, len);
|
||
|
||
return (char*)gateway;
|
||
}
|
||
|
||
int spotifyVer = -1;
|
||
int spotifyVerEnd = -1;
|
||
int Utils::GetSpotifyVersion()
|
||
{
|
||
if (spotifyVer != -1)
|
||
return spotifyVer;
|
||
|
||
LPCWSTR lpszFilePath = L"Spotify.exe";
|
||
DWORD dwDummy;
|
||
DWORD dwFVISize = GetFileVersionInfoSize(lpszFilePath, &dwDummy);
|
||
LPBYTE lpVersionInfo = new BYTE[dwFVISize];
|
||
GetFileVersionInfo(lpszFilePath, 0, dwFVISize, lpVersionInfo);
|
||
UINT uLen;
|
||
VS_FIXEDFILEINFO* lpFfi;
|
||
VerQueryValue(lpVersionInfo, _T("\\"), (LPVOID*)&lpFfi, &uLen);
|
||
DWORD dwFileVersionMS = lpFfi->dwFileVersionMS;
|
||
DWORD dwFileVersionLS = lpFfi->dwFileVersionLS;
|
||
delete[] lpVersionInfo;
|
||
|
||
DWORD dwLeftMost = HIWORD(dwFileVersionMS);
|
||
DWORD dwSecondLeft = LOWORD(dwFileVersionMS);
|
||
DWORD dwSecondRight = HIWORD(dwFileVersionLS);
|
||
DWORD dwRightMost = LOWORD(dwFileVersionLS);
|
||
|
||
spotifyVerEnd = dwRightMost;
|
||
return spotifyVer = dwSecondRight;
|
||
}
|
||
|
||
std::string Utils::HexString(BYTE* data, int len)
|
||
{
|
||
std::stringstream ss;
|
||
ss << std::hex;
|
||
|
||
for (int i(0); i < len; ++i)
|
||
ss << std::setw(2) << std::setfill('0') << (int)data[i];
|
||
|
||
return ss.str();
|
||
}
|
||
|
||
static const std::string illegalChars = "\\/:?\"<>|";
|
||
void Utils::RemoveForbiddenChar(std::string* str)
|
||
{
|
||
std::string::iterator it;
|
||
|
||
for (it = str->begin(); it < str->end(); ++it)
|
||
{
|
||
bool found = illegalChars.find(*it) != std::string::npos;
|
||
|
||
if (found)
|
||
*it = '_';
|
||
}
|
||
}
|
||
|
||
void Utils::RemoveForbiddenCharW(std::wstring* str)
|
||
{
|
||
std::wstring::iterator it;
|
||
|
||
for (it = str->begin(); it < str->end(); ++it)
|
||
{
|
||
bool found = illegalChars.find(*it) != std::string::npos;
|
||
|
||
if (found)
|
||
*it = '_';
|
||
}
|
||
}
|
||
|
||
std::wstring Utils::Utf8ToUtf16(const std::string& str)
|
||
{
|
||
std::wstring convertedString;
|
||
int requiredSize = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, 0, 0);
|
||
if (requiredSize > 0)
|
||
{
|
||
std::vector<wchar_t> buffer(requiredSize);
|
||
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &buffer[0], requiredSize);
|
||
convertedString.assign(buffer.begin(), buffer.end() - 1);
|
||
}
|
||
|
||
return convertedString;
|
||
}
|
||
|
||
struct SongInfo
|
||
{
|
||
std::string title, artist, album, cover;
|
||
} songInfo;
|
||
|
||
static const std::string albumSearchPattern = "\x68\x65\x69\x67\x68\x74\x22\x20\x3A\x20\x36\x34\x30";
|
||
static const std::wstring songDirRoot = L"Downloads";
|
||
static std::wstring songDir = songDirRoot;
|
||
void Utils::DownloadSong(std::string fileId, std::string trackUri, std::string key, std::string authToken)
|
||
{
|
||
std::cout << "Downloading song..." << std::endl;
|
||
|
||
/*std::cout << "fileId = " << fileId << std::endl;
|
||
std::cout << "trackUri = " << trackUri << std::endl;
|
||
std::cout << "key = " << key << std::endl;
|
||
std::cout << "authToken = " << authToken << std::endl;*/
|
||
|
||
// Get storage resolve from Spotify
|
||
std::string srStr = DownloadSpotifyUrl("spclient.wg.spotify.com",
|
||
"/storage-resolve/files/audio/interactive_prefetch/" + fileId + "?product=0", authToken);
|
||
|
||
if (srStr.substr(0, 5).compare("Error") == 0)
|
||
{
|
||
std::cout << srStr << std::endl;
|
||
return;
|
||
}
|
||
|
||
// Parse storage resolve response to get the encrypted song data's URL
|
||
std::string songHost = (srStr.substr(srStr.find("https://") + 8))
|
||
.erase(srStr.substr(srStr.find("https://") + 8).find("/audio/"));
|
||
std::string songPath = srStr.substr(srStr.find("/audio/")).erase(srStr.substr(srStr.find("/audio/")).find("=") + 85);
|
||
|
||
// Download encrypted song data from Spotify
|
||
std::string songStr = DownloadSpotifyUrl(songHost, songPath, "");
|
||
|
||
//std::cout << "URL: " << songHost + songPath << std::endl;
|
||
|
||
if (songStr.substr(0, 6).compare("<HTML>") == 0)
|
||
{
|
||
std::cout << "Error: " + songStr << std::endl;
|
||
return;
|
||
}
|
||
|
||
// Decrypt encrypted song data
|
||
struct AES_ctx ctx;
|
||
AES_init_ctx_iv(&ctx, reinterpret_cast<const uint8_t*>(&key[0]), IV);
|
||
AES_CTR_xcrypt_buffer(&ctx, reinterpret_cast<uint8_t*>(&songStr[0]), songStr.size());
|
||
|
||
// Remove custom Spotify Ogg page from beginning of file
|
||
songStr = songStr.substr(songStr.find("<EFBFBD><EFBFBD><EFBFBD><EFBFBD>OggS") + 4);
|
||
|
||
if (!trackUri.empty())
|
||
{
|
||
std::string metadata = DownloadSpotifyUrl("api.spotify.com", "/v1/tracks/"
|
||
+ trackUri.substr(trackUri.find("spotify:track:") + 14), authToken);
|
||
|
||
songInfo.title = strtok((char*)(metadata.substr(metadata.find("is_local") + 55)).c_str(), "\"");
|
||
songInfo.artist = strtok((char*)(metadata.substr(metadata.find("name") + 9)).c_str(), "\"");
|
||
songInfo.album = strtok((char*)(metadata.substr(metadata.find(albumSearchPattern) + 404)).c_str(), "\"");
|
||
songInfo.cover = strtok((char*)(metadata.substr(metadata.find("height") + 30)).c_str(), "\"");
|
||
|
||
/*std::cout << "<> <> <> <> <> <> <> <> <> <> <> <> <> <>" << std::endl;
|
||
std::cout << metadata << std::endl;
|
||
std::cout << "<> <> <> <> <> <> <> <> <> <> <> <> <> <>" << std::endl;
|
||
std::cout << "=========================================" << std::endl;
|
||
std::cout << "title: " << songInfo.title << std::endl;
|
||
std::cout << "artist: " << songInfo.artist << std::endl;
|
||
std::cout << "album: " << songInfo.album << std::endl;
|
||
std::cout << "title: " << songInfo.cover << std::endl;
|
||
std::cout << "=========================================" << std::endl;*/
|
||
|
||
std::wstring tempDirArtist = Utf8ToUtf16(songInfo.artist);
|
||
RemoveForbiddenCharW(&tempDirArtist);
|
||
|
||
songDir = songDirRoot;
|
||
if (!CreateDirectoryW(songDir.c_str(), NULL) && ERROR_ALREADY_EXISTS != GetLastError())
|
||
std::cout << "Couldn't create main downloads directory!" << std::endl;
|
||
|
||
if (CreateDirectoryW(std::wstring(songDir + L"\\" + tempDirArtist).c_str(), NULL)
|
||
|| ERROR_ALREADY_EXISTS == GetLastError())
|
||
{
|
||
std::wstring tempDirAlbum = Utf8ToUtf16(songInfo.album);
|
||
RemoveForbiddenCharW(&tempDirAlbum);
|
||
|
||
if (CreateDirectoryW(std::wstring(songDir + L"\\" + tempDirArtist + std::wstring(L"\\")
|
||
+ tempDirAlbum).c_str(), NULL) || ERROR_ALREADY_EXISTS == GetLastError())
|
||
{
|
||
songDir += L"\\" + tempDirArtist + std::wstring(L"\\") + tempDirAlbum;
|
||
|
||
std::wstring tempDirSong = Utf8ToUtf16(songInfo.title);
|
||
RemoveForbiddenCharW(&tempDirSong);
|
||
|
||
std::ofstream songFileOut(songDir + L".\\" + tempDirArtist + L" - " + tempDirSong + L".ogg",
|
||
std::ios_base::binary);
|
||
songFileOut.write(songStr.c_str(), songStr.size());
|
||
songFileOut.close();
|
||
|
||
std::cout << "Finished downloading: " << songInfo.artist << " - \"" << songInfo.title << "\"!" << std::endl;
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
std::cout << "Couldn't create album directory!" << std::endl;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
std::cout << "Couldn't create artist directory!" << std::endl;
|
||
}
|
||
|
||
std::cout << "Could not finish downloading song!" << std::endl;
|
||
}
|
||
}
|
||
|
||
std::string GetLastErrorAsString()
|
||
{
|
||
//Get the error message, if any.
|
||
DWORD errorMessageID = ::GetLastError();
|
||
if (errorMessageID == 0)
|
||
return std::string(); //No error message has been recorded
|
||
|
||
LPSTR messageBuffer = nullptr;
|
||
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
|
||
|
||
std::string message(messageBuffer, size);
|
||
|
||
//Free the buffer.
|
||
LocalFree(messageBuffer);
|
||
|
||
return message;
|
||
}
|
||
|
||
std::string Utils::DownloadSpotifyUrl(std::string host, std::string path, std::string authToken)
|
||
{
|
||
std::string response;
|
||
std::string authHeader = (authToken.empty()) ? "" : "Authorization: Bearer " + authToken;
|
||
std::string userAgent = "Spotify/11" + std::to_string(spotifyVer) + std::string("00")
|
||
+ std::to_string(spotifyVerEnd) + std::string(" Win32/Windows 10 (10.0.19042; x64)");
|
||
HINTERNET hSession, hConnect, hRequest;
|
||
BOOL bRequestSent;
|
||
const int bufferSize = 1024;
|
||
|
||
hSession = InternetOpenA(userAgent.c_str(), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
|
||
|
||
if (hSession == NULL)
|
||
return "Error: Could not initialize request!";
|
||
|
||
hConnect = InternetConnectA(hSession, host.c_str(), 80, NULL, NULL, INTERNET_SERVICE_HTTP, 0,
|
||
NULL);
|
||
|
||
if (hConnect == NULL)
|
||
return "Error: Could not create connect!";
|
||
|
||
hRequest = HttpOpenRequestA(hConnect, "GET", path.c_str(), NULL, NULL, NULL, INTERNET_FLAG_NO_AUTH, 0);
|
||
|
||
if (hRequest == NULL)
|
||
return "Error: Could not create open request!";
|
||
|
||
HttpAddRequestHeadersA(hRequest, authHeader.c_str(), -1, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
|
||
bRequestSent = HttpSendRequestA(hRequest, NULL, 0, NULL, 0);
|
||
|
||
if (!bRequestSent)
|
||
return "Error: Could not send request!";
|
||
|
||
char tmpBuffer[bufferSize];
|
||
BOOL canRead = true;
|
||
DWORD bytesRead = -1;
|
||
|
||
while (InternetReadFile(hRequest, tmpBuffer, bufferSize, &bytesRead) && bytesRead)
|
||
response.append(tmpBuffer, bytesRead);
|
||
|
||
InternetCloseHandle(hRequest);
|
||
|
||
return response;
|
||
}
|
||
|
||
std::string Utils::DownloadUrlOld(std::string host, std::string path, std::string authToken)
|
||
{
|
||
std::string authHeader;
|
||
std::string userAgent = "Spotify/11" + std::to_string(spotifyVer) + std::string("00")
|
||
+ std::to_string(spotifyVerEnd) + std::string(" Win32/Windows 10 (10.0.19042; x64)");
|
||
HINTERNET hInternet = InternetOpenA(userAgent.c_str(), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
|
||
HINTERNET hConnect;
|
||
HINTERNET hRequest;
|
||
BOOL bRequestSent;
|
||
|
||
authHeader = "Authorization: Bearer " + authToken;
|
||
|
||
if (hInternet == NULL)
|
||
return "Error: Could not initialize request!";
|
||
|
||
hConnect = InternetConnectA(hInternet, host.c_str(), 80, NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL);
|
||
|
||
if (hConnect == NULL)
|
||
return "Error: Could not create connect!";
|
||
|
||
hRequest = HttpOpenRequestA(hConnect, "GET", path.c_str(), NULL, NULL, NULL, INTERNET_FLAG_NO_AUTH, 0);
|
||
|
||
if (hRequest == NULL)
|
||
return "Error: Could not create open request!";
|
||
|
||
HttpAddRequestHeadersA(hRequest, authHeader.c_str(), -1, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
|
||
bRequestSent = HttpSendRequestA(hRequest, NULL, 0, NULL, 0);
|
||
|
||
if (!bRequestSent)
|
||
return "Error: Could not send request!";
|
||
|
||
std::string response;
|
||
const int bufferSize = 1024;
|
||
char tmpBuffer[bufferSize];
|
||
|
||
BOOL canRead = true;
|
||
DWORD bytesRead = -1;
|
||
|
||
while (canRead && bytesRead != 0)
|
||
{
|
||
canRead = InternetReadFile(hRequest, tmpBuffer, bufferSize, &bytesRead);
|
||
response.append(tmpBuffer, bytesRead);
|
||
}
|
||
|
||
return response;
|
||
}
|
||
|
||
bool Utils::BadPtr(void* ptr)
|
||
{
|
||
MEMORY_BASIC_INFORMATION mbi = { 0 };
|
||
if (VirtualQuery(ptr, &mbi, sizeof(mbi)))
|
||
{
|
||
DWORD mask = (PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE
|
||
| PAGE_EXECUTE_WRITECOPY);
|
||
bool b = !(mbi.Protect & mask);
|
||
|
||
if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS))
|
||
b = true;
|
||
|
||
return b;
|
||
}
|
||
|
||
return true;
|
||
} |