WiiUIdent/source/screens/SubmitScreen.cpp

283 lines
10 KiB
C++

#include "SubmitScreen.hpp"
#include "Gfx.hpp"
#include "Utils.hpp"
#include "system/OTP.hpp"
#include "system/SEEPROM.hpp"
#include "system/MemoryDevice.hpp"
#include <coreinit/bsp.h>
#include <coreinit/mcp.h>
#include <curl/curl.h>
#include <cacert_pem.h>
// POST data
struct post_data {
char system_model[16]; // [0x000] seeprom[0xB8]
char system_serial[16]; // [0x010] seeprom[0xAC] + seeprom[0xB0] - need to mask the last 3 digits
uint8_t mfg_date[6]; // [0x020] seeprom[0xC4]
uint8_t productArea; // [0x026]
uint8_t gameRegion; // [0x027]
uint32_t sec_level; // [0x028] otp[0x080]
uint16_t boardType; // [0x02C] seeprom[0x21]
uint16_t boardRevision; // [0x02E] seeprom[0x22]
uint16_t bootSource; // [0x030] seeprom[0x23]
uint16_t ddr3Size; // [0x032] seeprom[0x24]
uint16_t ddr3Speed; // [0x034] seeprom[0x25]
uint16_t sataDevice; // [0x036] seeprom[0x2C]
uint16_t consoleType; // [0x038] seeprom[0x2D]
uint16_t reserved1; // [0x03A]
uint32_t bsp_rev; // [0x03C] bspGetHardwareVersion();
uint16_t ddr3Vendor; // [0x040] seeprom[0x29]
uint8_t reserved2[62]; // [0x042]
// [0x080]
struct {
uint32_t cid[4]; // [0x080] CID
uint32_t mid_prv; // [0x090] Manufacturer and product revision
uint32_t blockSize; // [0x094] Block size
uint64_t numBlocks; // [0x098] Number of blocks
char name1[128]; // [0x0A0] Product name
} mlc;
// [0x120]
uint8_t otp_sha256[32]; // [0x120] OTP SHA-256 hash (to prevent duplicates)
}; // size == 0x140 (320)
WUT_CHECK_SIZE(post_data, 0x140);
struct post_data_hashed {
struct post_data data;
uint8_t post_sha256[32]; // [0x140] SHA-256 hash of post_data, with adjustments
}; // size == 0x160 (352)
WUT_CHECK_SIZE(post_data_hashed, 0x160);
namespace
{
const char* desc =
"This will submit statistical data to the developers of WiiUIdent,\n"
"which will help to determine various statistics about Wii U consoles,\n"
"e.g. eMMC manufacturers. The submitted data may be publicly accessible\n"
"but personally identifying information will be kept confidential.\n"
"\n"
"Information that will be submitted:\n"
"\uff65 System model\n"
"\uff65 System serial number (excluding the last 3 digits)\n"
"\uff65 Manufacturing date\n"
"\uff65 Region information\n"
"\uff65 Security level (keyset), sataDevice, consoleType, BSP revision\n"
"\uff65 boardType, boardRevision, bootSource, ddr3Size, ddr3Speed, ddr3Vendor\n"
"\uff65 MLC manufacturer, revision, name, size, and CID\n"
"\uff65 SHA-256 hash of OTP (to prevent duplicates)\n"
"\n"
"Do you want to submit your console's system data?\n";
}
SubmitScreen::SubmitScreen()
{
}
SubmitScreen::~SubmitScreen()
{
}
void SubmitScreen::Draw()
{
DrawTopBar("Submit System Information");
if (state == STATE_INFO) {
Gfx::Print(32, 75 + 32, 40, Gfx::COLOR_TEXT, desc);
DrawBottomBar(nullptr, "\ue044 Exit", "\ue001 Back / \ue000 Submit");
} else if (state == STATE_SUBMITTING) {
Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT / 2, 64, Gfx::COLOR_TEXT, "Submitting info...", Gfx::ALIGN_CENTER);
DrawBottomBar("Please wait...", nullptr, nullptr);
} else if (state == STATE_SUBMITTED) {
if (!error.empty() && response.empty()) {
Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT / 2, 40, Gfx::COLOR_ERROR, Utils::sprintf("Error!\n%s", error.c_str()), Gfx::ALIGN_CENTER);
Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT - 75 - 32, 40, Gfx::COLOR_TEXT,
"Failed to submit system data. Please report a bug on GitHub:\n"
"https://github.com/GaryOderNichts/WiiUIdent/issues", Gfx::ALIGN_HORIZONTAL | Gfx::ALIGN_BOTTOM);
} else if (!response.empty()) {
Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT / 2, 40, Gfx::COLOR_TEXT, response, Gfx::ALIGN_CENTER);
if (!error.empty()) {
Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT - 75 - 32, 40, Gfx::COLOR_TEXT,
"Failed to submit system data. Please report a bug on GitHub:\n"
"https://github.com/GaryOderNichts/WiiUIdent/issues", Gfx::ALIGN_HORIZONTAL | Gfx::ALIGN_BOTTOM);
} else {
Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT - 75 - 32, 40, Gfx::COLOR_TEXT,
"System data submitted successfully. Check out the Wii U console database at:\n"
"https://" DATABASE_URL "/", Gfx::ALIGN_HORIZONTAL | Gfx::ALIGN_BOTTOM);
}
} else {
Gfx::Print(Gfx::SCREEN_WIDTH / 2, Gfx::SCREEN_HEIGHT / 2, 64, Gfx::COLOR_TEXT, "No response.", Gfx::ALIGN_CENTER);
}
DrawBottomBar(nullptr, "\ue044 Exit", "\ue001 Back");
}
}
bool SubmitScreen::Update(VPADStatus& input)
{
if (state == STATE_INFO) {
if (input.trigger & VPAD_BUTTON_A) {
state = STATE_SUBMITTING;
} else if (input.trigger & VPAD_BUTTON_B) {
return false;
}
} else if (state == STATE_SUBMITTING) {
SubmitSystemData();
state = STATE_SUBMITTED;
} else if (state == STATE_SUBMITTED) {
if (input.trigger & VPAD_BUTTON_B) {
return false;
}
}
return true;
}
size_t SubmitScreen::CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userp)
{
size_t realsize = size * nmemb;
SubmitScreen* screen = (SubmitScreen*)userp;
if (screen->response.size() < 4096) { // let's add a limit here to be safe
screen->response += std::string((char*) contents, (char*) contents + realsize);
}
return realsize;
}
void SubmitScreen::SubmitSystemData()
{
const auto& otp = OTP::Get();
const auto& seeprom = SEEPROM::Get();
WUT_ALIGNAS(0x40) MCPSysProdSettings sysProd{};
int32_t mcpHandle = MCP_Open();
if (mcpHandle >= 0) {
// Don't bother checking res here, if it fails sysProd is zeroed
MCP_GetSysProdSettings(mcpHandle, &sysProd);
MCP_Close(mcpHandle);
}
const MemoryDevice* mlcDev = nullptr;
for (const MemoryDevice& dev : MemoryDevice::GetDevices()) {
if (dev.GetType() == MemoryDevice::TYPE_MLC) {
mlcDev = &dev;
}
}
post_data_hashed pdh{};
post_data* pd = &pdh.data;
memcpy(pd->system_model, seeprom.sys_prod.model_numer, sizeof(pd->system_model));
memcpy(pd->mfg_date, &seeprom.prod_info.prod_year, sizeof(pd->mfg_date));
pd->sec_level = otp.wiiUBank.securityLevel;
pd->boardType = seeprom.bc.boardType;
pd->boardRevision = seeprom.bc.boardRevision;
pd->bootSource = seeprom.bc.bootSource;
pd->ddr3Size = seeprom.bc.ddr3Size;
pd->ddr3Speed = seeprom.bc.ddr3Speed;
pd->ddr3Vendor = seeprom.bc.ddr3Vendor;
pd->sataDevice = seeprom.bc.sataDevice;
pd->consoleType = seeprom.bc.consoleType;
if (bspGetHardwareVersion(&pd->bsp_rev) != 0) {
pd->bsp_rev = 0;
}
// System serial number
// NOTE: Assuming code+serial doesn't exceed 15 chars (plus NULL).
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
snprintf(pd->system_serial, sizeof(pd->system_serial), "%s%s",
seeprom.sys_prod.code_id, seeprom.sys_prod.serial_id);
#pragma GCC diagnostic pop
// Mask the last 3 digits of the system serial number.
for (unsigned int i = sizeof(pd->system_serial)-1; i > 3; i--) {
if (pd->system_serial[i] <= 0x20) {
pd->system_serial[i] = 0;
continue;
}
// Found printable text.
// Mask the last three digits.
pd->system_serial[i-0] = '*';
pd->system_serial[i-1] = '*';
pd->system_serial[i-2] = '*';
break;
}
if (sysProd.product_area) {
pd->productArea = __builtin_ctz(sysProd.product_area);
}
pd->gameRegion = sysProd.game_region;
if (mlcDev) {
memcpy(pd->mlc.cid, mlcDev->GetCID().data(), sizeof(pd->mlc.cid));
pd->mlc.mid_prv = mlcDev->GetMID() << 16 | mlcDev->GetPRV();
pd->mlc.numBlocks = mlcDev->GetNumBlocks();
pd->mlc.blockSize = mlcDev->GetBlockSize();
strncpy(pd->mlc.name1, mlcDev->GetName().c_str(), sizeof(pd->mlc.name1) - 1);
}
if (Utils::SHA256(&otp, sizeof(otp), pd->otp_sha256, sizeof(pd->otp_sha256)) != 0) {
error = "Failed to hash otp data";
return;
}
char hashbuf[sizeof(*pd) + 64];
memcpy(hashbuf, pd, sizeof(*pd));
memcpy(hashbuf + sizeof(*pd), "This will submit statistical data to the developers of recovery_menu", 64);
if (Utils::SHA256(hashbuf, sizeof(hashbuf), pdh.post_sha256, sizeof(pdh.post_sha256)) != 0) {
error = "Failed to hash data";
return;
}
CURL* curl = curl_easy_init();
if (!curl) {
error = "Failed to init curl!";
return;
}
// setup post
curl_easy_setopt(curl, CURLOPT_USERAGENT, "WiiUIdent/" APP_VERSION);
curl_easy_setopt(curl, CURLOPT_URL, "https://" DATABASE_URL "/add-system.php");
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, sizeof(pdh));
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, &pdh);
// set content type to octet stream
struct curl_slist* list = nullptr;
list = curl_slist_append(list, "Content-Type: application/octet-stream");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
// set write callback for response
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
// load certs
struct curl_blob blob{};
blob.data = (void *) cacert_pem;
blob.len = cacert_pem_size;
blob.flags = CURL_BLOB_COPY;
curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
error = Utils::sprintf("Failed to upload data:\n%s (%d)", curl_easy_strerror(res), res);
curl_easy_cleanup(curl);
return;
}
long code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
if (code >= 400) {
error = Utils::sprintf("Server responded with %d!", code);
}
curl_easy_cleanup(curl);
return;
}