From 3e8284ace88a86e7bbe9658d3cf6a568876509cf Mon Sep 17 00:00:00 2001 From: Byte Date: Thu, 25 Jun 2026 23:04:35 +0200 Subject: [PATCH] fix: Minecraft Store 1; feat: Community-Made Packs integration --- .../Common/UI/UIScene_DLCMainMenu.cpp | 12 +- .../Common/UI/UIScene_DLCMainMenu.h | 38 +-- .../Common/UI/UIScene_DLCOffersMenu.cpp | 309 +++++++++++++----- .../Common/UI/UIScene_DLCOffersMenu.h | 21 +- 4 files changed, 267 insertions(+), 113 deletions(-) diff --git a/Minecraft.Client/Common/UI/UIScene_DLCMainMenu.cpp b/Minecraft.Client/Common/UI/UIScene_DLCMainMenu.cpp index fe531c99..4c37dc50 100644 --- a/Minecraft.Client/Common/UI/UIScene_DLCMainMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_DLCMainMenu.cpp @@ -3,7 +3,7 @@ #include "UIScene_DLCMainMenu.h" UIScene_DLCMainMenu::UIScene_DLCMainMenu(int iPad, void* initData, UILayer* parentLayer) - : UIScene(iPad, parentLayer) +: UIScene(iPad, parentLayer) { initialiseMovie(); app.SetLiveLinkRequired(true); @@ -14,10 +14,11 @@ UIScene_DLCMainMenu::UIScene_DLCMainMenu(int iPad, void* initData, UILayer* pare if(m_loadedResolution == eSceneResolution_1080) m_labelXboxStore.init(L""); - m_buttonListOffers.addItem(L"All Packs", 0); - m_buttonListOffers.addItem(L"Skin Packs", 1); - m_buttonListOffers.addItem(L"Texture Packs", 2); - m_buttonListOffers.addItem(L"Mash-up Packs", 3); + m_buttonListOffers.addItem(L"All Packs", 0); + m_buttonListOffers.addItem(L"Skin Packs", 1); + m_buttonListOffers.addItem(L"Texture Packs", 2); + m_buttonListOffers.addItem(L"Mash-up Packs", 3); + m_buttonListOffers.addItem(L"Community-Made Packs", 4); m_buttonListOffers.setFocus(true); m_Timer.setVisible(false); @@ -70,6 +71,7 @@ void UIScene_DLCMainMenu::handlePress(F64 controlId, F64 childId) DLCOffersParam* param = new DLCOffersParam(); param->iPad = m_iPad; param->iType = static_cast(childId); + ui.NavigateToScene(m_iPad, eUIScene_DLCOffersMenu, param); } diff --git a/Minecraft.Client/Common/UI/UIScene_DLCMainMenu.h b/Minecraft.Client/Common/UI/UIScene_DLCMainMenu.h index f23ee4b0..54dd6b54 100644 --- a/Minecraft.Client/Common/UI/UIScene_DLCMainMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_DLCMainMenu.h @@ -1,5 +1,4 @@ #pragma once - #include "UIScene.h" class UIScene_DLCMainMenu : public UIScene @@ -11,40 +10,39 @@ private: }; UIControl_DynamicButtonList m_buttonListOffers; - UIControl_Label m_labelOffers, m_labelXboxStore; - UIControl m_Timer; + UIControl_Label m_labelOffers, m_labelXboxStore; + UIControl m_Timer; + UI_BEGIN_MAP_ELEMENTS_AND_NAMES(UIScene) - UI_MAP_ELEMENT( m_buttonListOffers, "OffersList") - UI_MAP_ELEMENT( m_labelOffers, "OffersList_Title") - UI_MAP_ELEMENT( m_Timer, "Timer") + UI_MAP_ELEMENT(m_buttonListOffers, "OffersList") + UI_MAP_ELEMENT(m_labelOffers, "OffersList_Title") + UI_MAP_ELEMENT(m_Timer, "Timer") if(m_loadedResolution == eSceneResolution_1080) { - UI_MAP_ELEMENT( m_labelXboxStore, "XboxLabel" ) + UI_MAP_ELEMENT(m_labelXboxStore, "XboxLabel") } UI_END_MAP_ELEMENTS_AND_NAMES() - static int ExitDLCMainMenu(void *pParam,int iPad,C4JStorage::EMessageResult result); + static int ExitDLCMainMenu(void* pParam, int iPad, C4JStorage::EMessageResult result); -#if defined(__PS3__) || defined(__ORBIS__) || defined (__PSVITA__) - bool m_bCategoriesShown; +#if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__) + bool m_bCategoriesShown; #endif public: - UIScene_DLCMainMenu(int iPad, void *initData, UILayer *parentLayer); + UIScene_DLCMainMenu(int iPad, void* initData, UILayer* parentLayer); ~UIScene_DLCMainMenu(); - virtual void handleTimerComplete(int id); - virtual void handleGainFocus(bool navBack); - virtual EUIScene getSceneType() { return eUIScene_DLCMainMenu;} - virtual void tick(); - virtual void updateTooltips(); + virtual void handleTimerComplete(int id); + virtual void handleGainFocus(bool navBack); + virtual EUIScene getSceneType() { return eUIScene_DLCMainMenu; } + virtual void tick(); + virtual void updateTooltips(); protected: - // TODO: This should be pure virtual in this class virtual wstring getMoviePath(); public: - // INPUT - virtual void handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled); + virtual void handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool& handled); virtual void handlePress(F64 controlId, F64 childId); -}; +}; \ No newline at end of file diff --git a/Minecraft.Client/Common/UI/UIScene_DLCOffersMenu.cpp b/Minecraft.Client/Common/UI/UIScene_DLCOffersMenu.cpp index d6aff646..de8c7d57 100644 --- a/Minecraft.Client/Common/UI/UIScene_DLCOffersMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_DLCOffersMenu.cpp @@ -2,13 +2,44 @@ #include "UI.h" #include "UIScene_DLCOffersMenu.h" #include "../../../Minecraft.World/StringHelpers.h" -#include +#include +#pragma comment(lib, "winhttp.lib") + +#pragma pack(push,1) +struct LCEZipLocalHeader +{ + DWORD sig; + WORD verNeeded; + WORD flags; + WORD method; + WORD modTime; + WORD modDate; + DWORD crc32; + DWORD compSize; + DWORD uncompSize; + WORD fileNameLen; + WORD extraLen; +}; +#pragma pack(pop) + +struct LCEZipEntry +{ + std::string fname; + WORD method; + DWORD compSize; + DWORD uncompSize; + const BYTE* fileData; +}; + #include "miniz.h" #include "miniz.c" -#pragma comment(lib, "wininet.lib") -static const char* WORKSHOP_REGISTRY_URL = "https://network-server-7kuc.onrender.com/workshop/registry.json"; -static const char* WORKSHOP_RAW_BASE = "https://network-server-7kuc.onrender.com/workshop"; +static const char* WORKSHOP_REGISTRY_URL = "https://network-server-7kuc.onrender.com/workshop/registry.json"; +static const char* WORKSHOP_RAW_BASE = "https://network-server-7kuc.onrender.com/workshop"; +static const char* COMMUNITY_REGISTRY_LOCAL = "http://localhost:7601/registry.json"; +static const char* COMMUNITY_RAW_BASE_LOCAL = "http://localhost:7601"; +static const char* COMMUNITY_REGISTRY_REMOTE = "https://raw.githubusercontent.com/LCE-Hub/LCE-Workshop/refs/heads/main/registry.json"; +static const char* COMMUNITY_RAW_BASE_REMOTE = "https://github.com/LCE-Hub/LCE-Workshop/raw/refs/heads/main"; const char* UIScene_DLCOffersMenu::CategoryForIndex(int index) { @@ -17,7 +48,7 @@ const char* UIScene_DLCOffersMenu::CategoryForIndex(int index) case 1: return "Skin"; case 2: return "Texture"; case 3: return "Mashup"; - case 4: return "Mod"; + case 4: return nullptr; default: return nullptr; } } @@ -25,17 +56,51 @@ const char* UIScene_DLCOffersMenu::CategoryForIndex(int index) bool UIScene_DLCOffersMenu::FetchURL(const std::string& url, std::vector& outData) { outData.clear(); - HINTERNET hInet = InternetOpenA("LCEWorkshop/1.0", INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0); - if(!hInet) return false; - HINTERNET hUrl = InternetOpenUrlA(hInet, url.c_str(), nullptr, 0, - INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_SECURE, 0); - if(!hUrl) { InternetCloseHandle(hInet); return false; } - BYTE buf[8192]; + + bool isHttps = (url.size() >= 8 && _strnicmp(url.c_str(), "https://", 8) == 0); + const char* hostStart = url.c_str() + (isHttps ? 8 : 7); + const char* pathStart = strchr(hostStart, '/'); + + std::string host = pathStart ? std::string(hostStart, pathStart - hostStart) : std::string(hostStart); + std::string path = pathStart ? std::string(pathStart) : "/"; + + int port = isHttps ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT; + + std::wstring wHost(host.begin(), host.end()); + std::wstring wPath(path.begin(), path.end()); + + HINTERNET hSession = WinHttpOpen(L"LCEWorkshop/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + if(!hSession) return false; + + HINTERNET hConnect = WinHttpConnect(hSession, wHost.c_str(), static_cast(port), 0); + if(!hConnect) { WinHttpCloseHandle(hSession); return false; } + + DWORD flags = isHttps ? WINHTTP_FLAG_SECURE : 0; + HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", wPath.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, flags); + if(!hRequest) { WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return false; } + + DWORD redirectPolicy = WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS; + WinHttpSetOption(hRequest, WINHTTP_OPTION_REDIRECT_POLICY, &redirectPolicy, sizeof(redirectPolicy)); + + bool sent = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) != FALSE; + if(sent) sent = WinHttpReceiveResponse(hRequest, nullptr) != FALSE; + + if(!sent) { WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return false; } + + DWORD statusCode = 0; + DWORD statusSize = sizeof(statusCode); + WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusSize, WINHTTP_NO_HEADER_INDEX); + + if(statusCode != 200) { WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return false; } + + BYTE buf[65536]; DWORD dwRead = 0; - while(InternetReadFile(hUrl, buf, sizeof(buf), &dwRead) && dwRead > 0) + while(WinHttpReadData(hRequest, buf, sizeof(buf), &dwRead) && dwRead > 0) outData.insert(outData.end(), buf, buf + dwRead); - InternetCloseHandle(hUrl); - InternetCloseHandle(hInet); + + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); return !outData.empty(); } @@ -68,31 +133,14 @@ static void EnsureDirExists(const std::string& path) CreateDirectoryA(path.c_str(), nullptr); } -#pragma pack(push,1) -struct ZipLocalHeader -{ - DWORD sig; - WORD verNeeded; - WORD flags; - WORD method; - WORD modTime; - WORD modDate; - DWORD crc32; - DWORD compSize; - DWORD uncompSize; - WORD fileNameLen; - WORD extraLen; -}; -#pragma pack(pop) - static bool InflateBuffer(const BYTE* in, DWORD inSize, std::vector& out, DWORD uncompSize) { out.resize(uncompSize); - mz_stream strm = {}; - strm.next_in = in; - strm.avail_in = inSize; - strm.next_out = out.data(); - strm.avail_out = uncompSize; + mz_stream strm = {}; + strm.next_in = reinterpret_cast(in); + strm.avail_in = static_cast(inSize); + strm.next_out = reinterpret_cast(out.data()); + strm.avail_out = static_cast(uncompSize); if(mz_inflateInit2(&strm, -15) != MZ_OK) return false; int ret = mz_inflate(&strm, MZ_FINISH); mz_inflateEnd(&strm); @@ -100,48 +148,102 @@ static bool InflateBuffer(const BYTE* in, DWORD inSize, std::vector& out, return ret == MZ_STREAM_END; } -bool UIScene_DLCOffersMenu::InstallPack(const std::vector& zipData, const std::string& packName) +static std::vector CollectEntries(const std::vector& zipData) +{ + std::vector entries; + const BYTE* base = zipData.data(); + const BYTE* p = base; + const BYTE* end = base + zipData.size(); + + while(p + sizeof(LCEZipLocalHeader) <= end) + { + const LCEZipLocalHeader* hdr = reinterpret_cast(p); + if(hdr->sig == 0x02014b50 || hdr->sig == 0x06054b50) break; + if(hdr->sig != 0x04034b50) break; + + WORD flags = hdr->flags; + WORD method = hdr->method; + DWORD compSize = hdr->compSize; + DWORD uncompSize = hdr->uncompSize; + WORD fnLen = hdr->fileNameLen; + WORD exLen = hdr->extraLen; + + p += sizeof(LCEZipLocalHeader); + if(p + fnLen + exLen > end) break; + + LCEZipEntry e; + e.fname.assign(reinterpret_cast(p), fnLen); + p += fnLen + exLen; + + e.method = method; + e.compSize = compSize; + e.uncompSize = uncompSize; + e.fileData = p; + + if(flags & 0x0008) + { + if(method == 0) + { + compSize = uncompSize; + e.compSize = compSize; + } + else + { + const BYTE* scan = p; + while(scan + 4 <= end) + { + DWORD sig = *reinterpret_cast(scan); + if(sig == 0x08074b50 || sig == 0x04034b50 || sig == 0x02014b50) + { + compSize = static_cast(scan - p); + e.compSize = compSize; + break; + } + ++scan; + } + } + } + + if(p + e.compSize > end) break; + p += e.compSize; + + if(flags & 0x0008) + { + if(p + 4 <= end && *reinterpret_cast(p) == 0x08074b50) + p += 16; + else if(p + 12 <= end) + p += 12; + } + + for(size_t i = 0; i < e.fname.size(); i++) + if(e.fname[i] == '/') e.fname[i] = '\\'; + + if(!e.fname.empty()) + entries.push_back(e); + } + return entries; +} + +bool UIScene_DLCOffersMenu::InstallPack(const std::vector& zipData, const std::string& packName, bool isCommunity) { if(zipData.empty()) return false; - std::string baseDir = GetGameDir() + "Windows64Media\\DLC\\" + packName + "\\"; - EnsureDirExists(baseDir); + std::string outDir = isCommunity + ? GetGameDir() + "Windows64Media\\DLC\\" + : GetGameDir() + "Windows64Media\\DLC\\" + packName + "\\"; + EnsureDirExists(outDir); - const BYTE* p = zipData.data(); - const BYTE* end = p + zipData.size(); - int extracted = 0; + std::vector entries = CollectEntries(zipData); + if(entries.empty()) return false; - while(p + sizeof(ZipLocalHeader) <= end) + int extracted = 0; + + for(size_t i = 0; i < entries.size(); i++) { - const ZipLocalHeader* hdr = reinterpret_cast(p); + const LCEZipEntry& e = entries[i]; + if(e.fname.empty() || e.fname[e.fname.size() - 1] == '\\') continue; - if(hdr->sig == 0x02014b50 || hdr->sig == 0x06054b50) - break; - if(hdr->sig != 0x04034b50) - break; - - p += sizeof(ZipLocalHeader); - if(p + hdr->fileNameLen + hdr->extraLen > end) break; - - std::string fname(reinterpret_cast(p), hdr->fileNameLen); - p += hdr->fileNameLen + hdr->extraLen; - - if(p + hdr->compSize > end) break; - const BYTE* fileData = p; - p += hdr->compSize; - - if(fname.empty() || fname.back() == '/' || fname.back() == '\\') - continue; - - for(char& c : fname) if(c == '/') c = '\\'; - - size_t firstSlash = fname.find('\\'); - if(firstSlash != std::string::npos) - fname = fname.substr(firstSlash + 1); - - if(fname.empty()) continue; - - std::string outPath = baseDir + fname; + std::string outPath = outDir + e.fname; size_t lastSlash = outPath.rfind('\\'); if(lastSlash != std::string::npos) @@ -150,14 +252,14 @@ bool UIScene_DLCOffersMenu::InstallPack(const std::vector& zipData, const std::vector outBuf; bool ok = false; - if(hdr->method == 0) + if(e.method == 0) { - outBuf.assign(fileData, fileData + hdr->compSize); + outBuf.assign(e.fileData, e.fileData + e.compSize); ok = true; } - else if(hdr->method == 8) + else if(e.method == 8) { - ok = InflateBuffer(fileData, hdr->compSize, outBuf, hdr->uncompSize); + ok = InflateBuffer(e.fileData, e.compSize, outBuf, e.uncompSize); } if(ok && !outBuf.empty()) @@ -229,7 +331,7 @@ static void SkipValue(const char*& p) while(*p && *p != ',' && *p != '}' && *p != ']') ++p; } -bool UIScene_DLCOffersMenu::ParseRegistry(const std::string& json) +bool UIScene_DLCOffersMenu::ParseRegistry(const std::string& json, const std::string& rawBase) { m_packs.clear(); const char* p = json.c_str(); @@ -308,8 +410,14 @@ bool UIScene_DLCOffersMenu::ParseRegistry(const std::string& json) } if(*p == '}') ++p; + bool allDLC = !pack.zips.empty(); + for(int i = 0; i < (int)pack.zips.size(); i++) + if(pack.zips[i].destToken != "{DLCDir}") { allDLC = false; break; } + + if(!allDLC) continue; + if(!pack.id.empty() && !thumbFile.empty()) - pack.thumbnailUrl = std::string(WORKSHOP_RAW_BASE) + "/" + pack.id + "/" + thumbFile; + pack.thumbnailUrl = rawBase + "/" + pack.id + "/" + thumbFile; if(!pack.id.empty()) m_packs.push_back(pack); @@ -321,6 +429,7 @@ bool UIScene_DLCOffersMenu::ParseRegistry(const std::string& json) UIScene_DLCOffersMenu::UIScene_DLCOffersMenu(int iPad, void* initData, UILayer* parentLayer) : UIScene(iPad, parentLayer) , m_eFetchState(eFetch_Idle) + , m_bIsCommunity(false) , m_iCurrentPack(0) , m_iProductInfoIndex(0) , m_bIsSD(false) @@ -329,6 +438,7 @@ UIScene_DLCOffersMenu::UIScene_DLCOffersMenu(int iPad, void* initData, UILayer* { DLCOffersParam* param = static_cast(initData); m_iProductInfoIndex = param ? param->iType : 0; + m_bIsCommunity = (m_iProductInfoIndex == 4); initialiseMovie(); app.SetLiveLinkRequired(true); @@ -362,13 +472,42 @@ void UIScene_DLCOffersMenu::tick() m_eFetchState = eFetch_Error; - char urlWithBust[512]; - snprintf(urlWithBust, sizeof(urlWithBust), "%s?_=%llu", - WORKSHOP_REGISTRY_URL, - static_cast(GetTickCount64())); - std::string jsonStr; - if(!FetchURLString(urlWithBust, jsonStr) || !ParseRegistry(jsonStr)) + + if(m_bIsCommunity) + { + if(FetchURLString(COMMUNITY_REGISTRY_LOCAL, jsonStr)) + { + m_rawBase = COMMUNITY_RAW_BASE_LOCAL; + } + else + { + if(!FetchURLString(COMMUNITY_REGISTRY_REMOTE, jsonStr)) + { + m_labelOffers.setLabel(app.GetString(IDS_NO_DLCCATEGORIES)); + m_Timer.setVisible(false); + return; + } + m_rawBase = COMMUNITY_RAW_BASE_REMOTE; + } + } + else + { + char urlWithBust[512]; + snprintf(urlWithBust, sizeof(urlWithBust), "%s?_=%llu", + WORKSHOP_REGISTRY_URL, + static_cast(GetTickCount64())); + + if(!FetchURLString(urlWithBust, jsonStr)) + { + m_labelOffers.setLabel(app.GetString(IDS_NO_DLCCATEGORIES)); + m_Timer.setVisible(false); + return; + } + m_rawBase = WORKSHOP_RAW_BASE; + } + + if(!ParseRegistry(jsonStr, m_rawBase)) { m_labelOffers.setLabel(app.GetString(IDS_NO_DLCCATEGORIES)); m_Timer.setVisible(false); @@ -500,7 +639,7 @@ void UIScene_DLCOffersMenu::handlePress(F64 controlId, F64 childId) m_Timer.setVisible(true); const WorkshopZip& zip = pk->zips[0]; - std::string zipUrl = std::string(WORKSHOP_RAW_BASE) + "/" + pk->id + "/" + zip.filename; + std::string zipUrl = m_rawBase + "/" + pk->id + "/" + zip.filename; std::vector zipData; if(!FetchURL(zipUrl, zipData) || zipData.empty()) @@ -509,7 +648,7 @@ void UIScene_DLCOffersMenu::handlePress(F64 controlId, F64 childId) return; } - bool installed = InstallPack(zipData, packName); + bool installed = InstallPack(zipData, packName, m_bIsCommunity); m_Timer.setVisible(false); if(installed) diff --git a/Minecraft.Client/Common/UI/UIScene_DLCOffersMenu.h b/Minecraft.Client/Common/UI/UIScene_DLCOffersMenu.h index 771eb510..30a031b1 100644 --- a/Minecraft.Client/Common/UI/UIScene_DLCOffersMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_DLCOffersMenu.h @@ -1,10 +1,12 @@ #pragma once #include "UIScene.h" + struct WorkshopZip { std::string filename; std::string destToken; }; + struct WorkshopPack { std::string id; @@ -17,21 +19,27 @@ struct WorkshopPack std::string version; std::vector thumbnailData; bool thumbnailLoaded; + WorkshopPack() : thumbnailLoaded(false) {} }; + class UIScene_DLCOffersMenu : public UIScene { private: - enum EControls { eControl_OffersList }; + enum EControls { eControl_OffersList }; enum EFetchState { eFetch_Idle, eFetch_Ready, eFetch_Error }; + bool m_bIsSD; bool m_bHasPurchased; bool m_bIsSelected; + bool m_bIsCommunity; + UIControl_DLCList m_buttonListOffers; UIControl_Label m_labelOffers, m_labelPriceTag, m_labelXboxStore; UIControl_HTMLLabel m_labelHTMLSellText; UIControl_BitmapIcon m_bitmapIconOfferImage; UIControl m_Timer; + UI_BEGIN_MAP_ELEMENTS_AND_NAMES(UIScene) UI_MAP_ELEMENT(m_buttonListOffers, "OffersList") UI_MAP_ELEMENT(m_labelOffers, "OffersList_Title") @@ -44,28 +52,35 @@ private: UI_MAP_ELEMENT(m_labelXboxStore, "XboxLabel") } UI_END_MAP_ELEMENTS_AND_NAMES() + EFetchState m_eFetchState; + std::string m_rawBase; std::vector m_packs; std::vector m_filteredPacks; int m_iCurrentPack; int m_iProductInfoIndex; + static const char* CategoryForIndex(int index); static bool FetchURL(const std::string& url, std::vector& outData); static bool FetchURLString(const std::string& url, std::string& outStr); - bool ParseRegistry(const std::string& json); + bool ParseRegistry(const std::string& json, const std::string& rawBase); void PopulateList(); void UpdateDetailPanel(); - static bool InstallPack(const std::vector& zipData, const std::string& packName); + static bool InstallPack(const std::vector& zipData, const std::string& packName, bool isCommunity); static bool IsPackInstalled(const std::string& packName); + public: UIScene_DLCOffersMenu(int iPad, void* initData, UILayer* parentLayer); ~UIScene_DLCOffersMenu(); + static int ExitDLCOffersMenu(void* pParam, int iPad, C4JStorage::EMessageResult result); virtual EUIScene getSceneType() { return eUIScene_DLCOffersMenu; } virtual void tick(); virtual void updateTooltips(); + protected: virtual wstring getMoviePath(); + public: virtual void handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool& handled); virtual void handlePress(F64 controlId, F64 childId);