Files
MinecraftCommunityEdition-c…/Minecraft.Client/Windows64_DLCOffers.cpp
LazyByteDev b40d5e9d01 src stuff
2026-03-02 03:31:00 -05:00

459 lines
15 KiB
C++

#include "stdafx.h"
#ifdef _WINDOWS64
#include "Windows64_DLCOffers.h"
#include <winhttp.h>
#pragma comment(lib, "winhttp.lib")
#include <shlobj.h>
static bool ParseUrl(const wchar_t* url,
std::wstring& host, INTERNET_PORT& port,
std::wstring& path)
{
URL_COMPONENTS uc = {};
uc.dwStructSize = sizeof(uc);
uc.dwHostNameLength = (DWORD)-1;
uc.dwUrlPathLength = (DWORD)-1;
uc.dwExtraInfoLength = (DWORD)-1;
if (!WinHttpCrackUrl(url, 0, 0, &uc)) return false;
host.assign(uc.lpszHostName, uc.dwHostNameLength);
path.assign(uc.lpszUrlPath, uc.dwUrlPathLength);
if (uc.lpszExtraInfo && uc.dwExtraInfoLength)
path.append(uc.lpszExtraInfo, uc.dwExtraInfoLength);
port = uc.nPort;
return true;
}
bool Windows64_DLCOffers::FetchBytesFromUrl(const wchar_t* url,
PBYTE* ppData, DWORD* pdwSize)
{
*ppData = nullptr;
*pdwSize = 0;
printf("[DLC] FetchBytesFromUrl: '%ls'\n", url);
std::wstring host, path;
INTERNET_PORT port = 80;
if (!ParseUrl(url, host, port, path))
{
printf("[DLC] FetchBytesFromUrl: ParseUrl FAILED (err=%u)\n", GetLastError());
return false;
}
printf("[DLC] FetchBytesFromUrl: host='%ls' port=%u path='%ls'\n",
host.c_str(), (unsigned)port, path.c_str());
HINTERNET hSession = WinHttpOpen(L"W64DLC/1.0",
WINHTTP_ACCESS_TYPE_NO_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession)
{
printf("[DLC] FetchBytesFromUrl: WinHttpOpen FAILED (err=%u)\n", GetLastError());
return false;
}
HINTERNET hConn = WinHttpConnect(hSession, host.c_str(), port, 0);
if (!hConn)
{
printf("[DLC] FetchBytesFromUrl: WinHttpConnect FAILED (err=%u)\n", GetLastError());
WinHttpCloseHandle(hSession);
return false;
}
HINTERNET hReq = WinHttpOpenRequest(hConn, L"GET", path.c_str(),
nullptr, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
if (!hReq)
{
printf("[DLC] FetchBytesFromUrl: WinHttpOpenRequest FAILED (err=%u)\n", GetLastError());
WinHttpCloseHandle(hConn);
WinHttpCloseHandle(hSession);
return false;
}
if (!WinHttpSendRequest(hReq, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
{
printf("[DLC] FetchBytesFromUrl: WinHttpSendRequest FAILED (err=%u)\n", GetLastError());
WinHttpCloseHandle(hReq);
WinHttpCloseHandle(hConn);
WinHttpCloseHandle(hSession);
return false;
}
if (!WinHttpReceiveResponse(hReq, nullptr))
{
printf("[DLC] FetchBytesFromUrl: WinHttpReceiveResponse FAILED (err=%u)\n", GetLastError());
WinHttpCloseHandle(hReq);
WinHttpCloseHandle(hConn);
WinHttpCloseHandle(hSession);
return false;
}
DWORD dwStatusCode = 0;
DWORD dwStatusSize = sizeof(dwStatusCode);
WinHttpQueryHeaders(hReq,
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX,
&dwStatusCode, &dwStatusSize,
WINHTTP_NO_HEADER_INDEX);
printf("[DLC] FetchBytesFromUrl: HTTP status = %u\n", dwStatusCode);
if (dwStatusCode != 200)
{
printf("[DLC] FetchBytesFromUrl: non-200 response, aborting\n");
WinHttpCloseHandle(hReq);
WinHttpCloseHandle(hConn);
WinHttpCloseHandle(hSession);
return false;
}
std::vector<BYTE> buf;
DWORD dwRead = 0;
BYTE chunk[8192];
while (WinHttpReadData(hReq, chunk, sizeof(chunk), &dwRead) && dwRead)
buf.insert(buf.end(), chunk, chunk + dwRead);
printf("[DLC] FetchBytesFromUrl: read %zu bytes\n", buf.size());
bool bOk = false;
if (!buf.empty())
{
*ppData = new BYTE[buf.size()];
*pdwSize = (DWORD)buf.size();
memcpy(*ppData, buf.data(), buf.size());
bOk = true;
}
else
{
printf("[DLC] FetchBytesFromUrl: response body was empty\n");
}
WinHttpCloseHandle(hReq);
WinHttpCloseHandle(hConn);
WinHttpCloseHandle(hSession);
return bOk;
}
static bool WinHttpGetUrl(const wchar_t* fullUrl, PBYTE* ppData, DWORD* pdwSize)
{
*ppData = nullptr;
*pdwSize = 0;
URL_COMPONENTS uc;
ZeroMemory(&uc, sizeof(uc));
uc.dwStructSize = sizeof(uc);
wchar_t szHostName[256] = {};
wchar_t szUrlPath[1024] = {};
uc.lpszHostName = szHostName;
uc.dwHostNameLength = _countof(szHostName);
uc.lpszUrlPath = szUrlPath;
uc.dwUrlPathLength = _countof(szUrlPath);
if (!WinHttpCrackUrl(fullUrl, 0, 0, &uc))
{
printf("[DLC] WinHttpGetUrl: WinHttpCrackUrl failed for '%ls': %u\n", fullUrl, GetLastError());
return false;
}
HINTERNET hSession = WinHttpOpen(L"MC/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0);
if (!hSession) return false;
HINTERNET hConnect = WinHttpConnect(hSession, szHostName, uc.nPort, 0);
if (!hConnect) { WinHttpCloseHandle(hSession); return false; }
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", szUrlPath,
NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
if (!hRequest)
{
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return false;
}
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0, 0, 0) ||
!WinHttpReceiveResponse(hRequest, NULL))
{
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return false;
}
std::string body;
DWORD dwSize = 0;
do {
DWORD dwDownloaded = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize) || dwSize == 0) break;
char* buf = new char[dwSize];
if (WinHttpReadData(hRequest, buf, dwSize, &dwDownloaded))
body.append(buf, dwDownloaded);
delete[] buf;
} while (dwSize > 0);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
if (body.empty()) return false;
*pdwSize = (DWORD)body.size();
*ppData = new BYTE[*pdwSize];
memcpy(*ppData, body.data(), *pdwSize);
return true;
}
bool Windows64_DLCOffers::FetchImageFromUrl(const wchar_t* url, PBYTE* ppData, DWORD* pdwSize)
{
return WinHttpGetUrl(url, ppData, pdwSize);
}
void Windows64_DLCOffers::InstallOffer(int iIndex,
W64_INSTALL_CALLBACK pfnCallback,
void* pUserData)
{
if (iIndex < 0 || iIndex >= (int)m_offers.size()) return;
InstallCtx* ctx = new InstallCtx();
ctx->offer = m_offers[iIndex];
ctx->offerIndex = iIndex;
ctx->pfnCallback = pfnCallback;
ctx->pUserData = pUserData;
HANDLE hThread = CreateThread(nullptr, 0, InstallThreadProc, ctx, 0, nullptr);
if (hThread) CloseHandle(hThread);
else delete ctx;
}
DWORD WINAPI Windows64_DLCOffers::InstallThreadProc(LPVOID lpParam)
{
InstallCtx* ctx = reinterpret_cast<InstallCtx*>(lpParam);
bool bSuccess = false;
// <exe dir>\Windows64Media\DLC\<productID>\folder.pck
wchar_t wszExeDir[MAX_PATH] = {};
GetModuleFileNameW(nullptr, wszExeDir, MAX_PATH);
wchar_t* pLastSlash = wcsrchr(wszExeDir, L'\\');
if (pLastSlash) *(pLastSlash + 1) = L'\0';
wchar_t wszInstallDir[MAX_PATH];
_snwprintf_s(wszInstallDir, _countof(wszInstallDir), _TRUNCATE,
L"%lsWindows64Media\\DLC\\%ls",
wszExeDir, ctx->offer.wszProductID);
CreateDirectoryW(wszInstallDir, nullptr);
wchar_t wszFilePath[MAX_PATH];
_snwprintf_s(wszFilePath, _countof(wszFilePath), _TRUNCATE,
L"%ls\\folder.pck", wszInstallDir);
wchar_t wszDownloadUrl[512];
_snwprintf_s(wszDownloadUrl, _countof(wszDownloadUrl), _TRUNCATE,
L"http://127.0.0.1:3000/download/%ls", ctx->offer.wszProductID);
PBYTE pData = nullptr;
DWORD dwBytes = 0;
if (FetchBytesFromUrl(wszDownloadUrl, &pData, &dwBytes) && pData)
{
HANDLE hFile = CreateFileW(wszFilePath, GENERIC_WRITE, 0, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD dwWritten = 0;
WriteFile(hFile, pData, dwBytes, &dwWritten, nullptr);
CloseHandle(hFile);
bSuccess = (dwWritten == dwBytes);
}
delete[] pData;
}
if (bSuccess)
{
// Mark the offer as owned.
Windows64_DLCOffers::Get().SetPurchased(ctx->offerIndex);
// Must be set BEFORE m_iLastInstalled is updated so the game thread always
Windows64_DLCOffers::Get().SetPendingLoadPath(ctx->offerIndex, wszFilePath);
InterlockedExchange(&Windows64_DLCOffers::Get().m_iLastInstalled,
(LONG)ctx->offerIndex);
printf("[DLC] Installed '%ls' -> %ls\n",
ctx->offer.wszProductID, wszFilePath);
}
else
{
printf("[DLC] Install FAILED for '%ls'\n", ctx->offer.wszProductID);
}
if (ctx->pfnCallback)
ctx->pfnCallback(ctx->offer.wszProductID, bSuccess, ctx->pUserData);
delete ctx;
return 0;
}
void Windows64_DLCOffers::FetchFromServer()
{
printf("[DLC] FetchFromServer called\n");
m_offers.clear();
m_bReady = false;
HINTERNET hSession = WinHttpOpen(L"MC/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0);
if (!hSession) return;
HINTERNET hConnect = WinHttpConnect(hSession, L"localhost", 3000, 0);
if (!hConnect) { WinHttpCloseHandle(hSession); return; }
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/dlc",
NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
if (!hRequest)
{
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return;
}
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0, 0, 0) ||
!WinHttpReceiveResponse(hRequest, NULL))
{
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return;
}
std::string body;
DWORD dwSize = 0;
do {
DWORD dwDownloaded = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize) || dwSize == 0) break;
char* buf = new char[dwSize + 1];
ZeroMemory(buf, dwSize + 1);
if (WinHttpReadData(hRequest, buf, dwSize, &dwDownloaded))
body.append(buf, dwDownloaded);
delete[] buf;
} while (dwSize > 0);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
if (body.empty()) { printf("[DLC] Empty /dlc response\n"); return; }
printf("[DLC] /dlc response (%d bytes): %s\n", (int)body.size(), body.c_str());
auto toWide = [](const std::string& s, wchar_t* dst, int maxChars) {
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, dst, maxChars);
};
size_t pos = 0;
while ((pos = body.find('{', pos)) != std::string::npos)
{
W64_OFFER_INFO offer;
ZeroMemory(&offer, sizeof(offer));
auto readField = [&](const char* key) -> std::string {
std::string search = std::string("\"") + key + "\"";
size_t k = body.find(search, pos);
if (k == std::string::npos) return "";
size_t colon = body.find(':', k);
if (colon == std::string::npos) return "";
size_t q1 = body.find('"', colon);
if (q1 == std::string::npos) return "";
size_t q2 = body.find('"', q1 + 1);
if (q2 == std::string::npos) return "";
return body.substr(q1 + 1, q2 - q1 - 1);
};
auto readInt = [&](const char* key) -> int {
std::string search = std::string("\"") + key + "\"";
size_t k = body.find(search, pos);
if (k == std::string::npos) return 0;
size_t colon = body.find(':', k);
if (colon == std::string::npos) return 0;
size_t n = colon + 1;
while (n < body.size() && (body[n] == ' ' || body[n] == '\t')) n++;
return atoi(&body[n]);
};
toWide(readField("id"), offer.wszProductID, 64);
toWide(readField("name"), offer.wszOfferName, 128);
toWide(readField("description"), offer.wszSellText, 512);
toWide(readField("price"), offer.wszCurrencyPrice, 32);
toWide(readField("type"), offer.wszType, 32);
toWide(readField("bannerUrl"), offer.wszBannerUrl, 256);
offer.fUserHasPurchased = readInt("purchased");
offer.pbBannerData = nullptr;
offer.dwBannerBytes = 0;
if (offer.wszProductID[0] != L'\0')
{
printf("[DLC] Parsed: id='%ls' type='%ls' bannerUrl='%ls'\n",
offer.wszProductID, offer.wszType, offer.wszBannerUrl);
m_offers.push_back(offer);
}
pos = body.find('}', pos);
if (pos == std::string::npos) break;
pos++;
}
printf("[DLC] FetchFromServer complete - %d offers\n", (int)m_offers.size());
m_bReady = true;
}
void Windows64_DLCOffers::FetchBanners()
{
for (int i = 0; i < (int)m_offers.size(); i++)
{
W64_OFFER_INFO& offer = m_offers[i];
if (offer.wszBannerUrl[0] == L'\0') continue;
if (offer.pbBannerData != nullptr) continue;
PBYTE pbData = nullptr;
DWORD dwBytes = 0;
if (FetchImageFromUrl(offer.wszBannerUrl, &pbData, &dwBytes))
{
offer.pbBannerData = pbData;
offer.dwBannerBytes = dwBytes;
printf("[DLC] Banner fetched for '%ls' - %u bytes\n",
offer.wszProductID, dwBytes);
}
else
{
printf("[DLC] Banner fetch FAILED for '%ls' url='%ls'\n",
offer.wszProductID, offer.wszBannerUrl);
}
}
// Register all offers with the app now that banners are populated.
for (int i = 0; i < (int)m_offers.size(); i++)
{
const W64_OFFER_INFO& offer = m_offers[i];
eDLCContentType eType = e_DLC_SkinPack;
if (wcscmp(offer.wszType, L"MashUp") == 0) eType = e_DLC_MashupPacks;
else if (wcscmp(offer.wszType, L"TexturePack") == 0) eType = e_DLC_TexturePacks;
else if (wcscmp(offer.wszType, L"SkinPack") == 0) eType = e_DLC_SkinPack;
printf("[DLC] Registering W64 DLC: '%ls' type=%d\n",
offer.wszProductID, (int)eType);
app.RegisterW64DLC(
eType,
offer.wszProductID, // key AND banner texture name
offer.wszProductID, // banner texture name (used as lookup key in UpdateDisplay)
0, // iConfig
(unsigned int)i, // uiSortIndex
offer.pbBannerData,
offer.dwBannerBytes
);
}
}
#endif