Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
project(XiaomiOTA
project(MiTool-CLI
LANGUAGES CXX
VERSION 0.0.1
)
Expand All @@ -15,5 +15,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(OpenSSL REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(cpr REQUIRED)
find_package(fmt REQUIRED)
find_package(CLI11 REQUIRED)

add_subdirectory(src)
2 changes: 2 additions & 0 deletions conanfile.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
openssl/3.6.2
nlohmann_json/3.12.0
cpr/1.14.2
fmt/12.1.0
cli11/2.6.2
[generators]
CMakeDeps
CMakeToolchain
23 changes: 18 additions & 5 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
add_executable(xmota
main.cc
)
add_library(MiuiUpdate
MiuiUpdate/MiuiCrypto.cc
MiuiUpdate/MiuiFindUpdate.cc
MiuiUpdate/MiuiParseVersion.cc
)

add_executable(mitool
Main.cc
)

target_link_libraries(xmota
target_link_libraries(MiuiUpdate
PRIVATE openssl::openssl
PRIVATE cpr::cpr
PRIVATE nlohmann_json::nlohmann_json
)
)

target_link_libraries(mitool
PRIVATE nlohmann_json::nlohmann_json
PRIVATE fmt::fmt
PRIVATE CLI11::CLI11
PRIVATE MiuiUpdate
)
141 changes: 141 additions & 0 deletions src/Main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#include "MiuiUpdate/MiuiUpdate.h"

#include <CLI/CLI.hpp>
#include <fmt/base.h>
#include <nlohmann/json.hpp>

#include <string>
#include <vector>
#include <algorithm>
#include <cctype>

using json = nlohmann::json;

static constexpr const char *ASCII_ART =
"__ __ ___ _____ ___ ___ _ ____ _ ___ \n"
"| \\/ |_ _|_ _/ _ \\ / _ \\| | / ___| | |_ _|\n"
"| |\\/| || | | || | | | | | | | _____| | | | | | \n"
"| | | || | | || |_| | |_| | |__|_____| |___| |___ | | \n"
"|_| |_|___| |_| \\___/ \\___/|_____| \\____|_____|___|\n";

struct LatestUpdateInfo {
std::string device;
std::string currentRom;
std::string latestRom;
std::string androidVer;
std::string fileSize;
std::string applicableFrom;
std::string md5;
std::vector<std::string> changelog;
};

static std::string safeGet(const json &obj, const std::string &key) {
if (!obj.is_null() && obj.contains(key) && obj.at(key).is_string()) {
return obj.at(key).get<std::string>();
}
return "N/A";
}

static LatestUpdateInfo parseUpdateInfo(const std::string &jsonStr) {
const json data = json::parse(jsonStr);

const json &latestRom = data["LatestRom"];
const json &currentRom = data["CurrentRom"];
const json &incrementRom = data["IncrementRom"];

std::vector<std::string> changelog;
if (latestRom.contains("changelog")) {
const json &cl = latestRom.at("changelog");
if (cl.contains("System") && cl.at("System").contains("txt")) {
const json &txt = cl.at("System").at("txt");
if (txt.is_array()) {
for (const auto &entry : txt) {
if (entry.is_string()) {
changelog.push_back(entry.get<std::string>());
}
}
}
}
}

if (changelog.empty()) {
changelog.push_back("N/A");
}

return LatestUpdateInfo{
.device = safeGet(latestRom, "device"),
.currentRom = safeGet(currentRom, "version"),
.latestRom = safeGet(latestRom, "version"),
.androidVer = safeGet(latestRom, "codebase"),
.fileSize = safeGet(latestRom, "filesize"),
.applicableFrom = safeGet(incrementRom, "versionForApply"),
.md5 = safeGet(latestRom, "md5"),
.changelog = changelog,
};
}

static void printUpdateInfo(const LatestUpdateInfo &info) {
static constexpr const char *SEP =
"├─────────────────────────────────────────────────────────────────";

fmt::println(
"┌─────────────────────────────────────────────────────────────────");
fmt::println("│ MIUI OTA — Update Information");
fmt::println("{}", SEP);
fmt::println("│ Device : {}", info.device);
fmt::println("│ Current ROM : {}", info.currentRom);
fmt::println("│ Latest ROM : {}", info.latestRom);
fmt::println("│ Android version : {}", info.androidVer);
fmt::println("│ File size : {}", info.fileSize);
fmt::println("│ Applicable from : {}", info.applicableFrom);
fmt::println("│ MD5 : {}", info.md5);
fmt::println("{}", SEP);
fmt::println("│ Changelog:");
for (std::size_t i = 0; i < info.changelog.size(); ++i) {
fmt::println("│ {}. {}", i + 1, info.changelog[i]);
}
fmt::println(
"└─────────────────────────────────────────────────────────────────");
}

int main(int argc, char *argv[]) {
std::string osVersion;
std::string deviceCodename;
bool noBannerFlag{false};

CLI::App app{"A simple utility for finding, downloading, and installing "
"firmware updates"};

app.add_option("--os-version", osVersion,
"Specify the OS version currently installed on the device");
app.add_option("--device", deviceCodename, "Specify the device codename");
app.add_flag("--no-banner", noBannerFlag, "Disable ASCII startup banner");

CLI11_PARSE(app, argc, argv);

if (!noBannerFlag) {
fmt::println("{}", ASCII_ART);
}

std::transform(osVersion.begin(), osVersion.end(), osVersion.begin(), ::toupper);
std::transform(deviceCodename.begin(), deviceCodename.end(), deviceCodename.begin(), ::tolower);

MiuiUpdater updater;
const std::string jsonResponse =
updater.getLatestUpdate(deviceCodename, osVersion);

LatestUpdateInfo info;
try {
info = parseUpdateInfo(jsonResponse);
} catch (const json::parse_error &e) {
fmt::println("[ERROR] Failed to parse server response: {}", e.what());
return 1;
} catch (const json::out_of_range &e) {
fmt::println("[ERROR] Unexpected response structure: {}", e.what());
return 1;
}

printUpdateInfo(info);

return 0;
}
21 changes: 0 additions & 21 deletions src/MiuiOTA.h

This file was deleted.

121 changes: 121 additions & 0 deletions src/MiuiUpdate/MiuiCrypto.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#include "MiuiKeys.h"
#include "MiuiUpdate.h"

#include <openssl/aes.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>

#include <cstdint>
#include <iomanip>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

std::vector<std::uint8_t> MiuiUpdater::base64Decrypt(const std::string &text) {
BIO *bmem = BIO_new_mem_buf(text.data(), static_cast<int>(text.size()));
BIO *b64 = BIO_new(BIO_f_base64());

if (!bmem || !b64)
throw std::runtime_error("base64Decrypt: failed to create BIO");

BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO_push(b64, bmem);

std::vector<std::uint8_t> buffer(text.size());
int totalLen = 0;

while (true) {
int len = BIO_read(b64, buffer.data() + totalLen,
static_cast<int>(buffer.size()) - totalLen);
if (len <= 0)
break;
totalLen += len;
}

BIO_free_all(b64);
buffer.resize(totalLen);
return buffer;
}

std::string MiuiUpdater::base64Encrypt(std::vector<std::uint8_t> &data) {
BIO *b64 = BIO_new(BIO_f_base64());
BIO *bmem = BIO_new(BIO_s_mem());

BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO_push(b64, bmem);

BIO_write(b64, data.data(), static_cast<int>(data.size()));
BIO_flush(b64);

BUF_MEM *bptr = nullptr;
BIO_get_mem_ptr(bmem, &bptr);

std::string result(bptr->data, bptr->length);
BIO_free_all(b64);

return result;
}

std::vector<std::uint8_t>
MiuiUpdater::aes128cbcEncrypt(const std::string &data) {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx)
throw std::runtime_error("aes128cbcEncrypt: failed to create context");

if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), nullptr, MIUI_CRYPTO_KEY,
MIUI_CRYPTO_IV) != 1) {
EVP_CIPHER_CTX_free(ctx);
throw std::runtime_error("aes128cbcEncrypt: initialization error");
}

std::vector<std::uint8_t> ciphertext(data.size() + AES_BLOCK_SIZE);
int outLen1 = 0, outLen2 = 0;

if (EVP_EncryptUpdate(ctx, ciphertext.data(), &outLen1,
reinterpret_cast<const unsigned char *>(data.data()),
static_cast<int>(data.size())) != 1) {
EVP_CIPHER_CTX_free(ctx);
throw std::runtime_error("aes128cbcEncrypt: encryption error (Update)");
}

if (EVP_EncryptFinal_ex(ctx, ciphertext.data() + outLen1, &outLen2) != 1) {
EVP_CIPHER_CTX_free(ctx);
throw std::runtime_error("aes128cbcEncrypt: encryption error (Final)");
}

EVP_CIPHER_CTX_free(ctx);
ciphertext.resize(outLen1 + outLen2);
return ciphertext;
}

std::string MiuiUpdater::aes128cbcDecrypt(std::vector<uint8_t> &data) {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx)
throw std::runtime_error("aes128cbcDecrypt: failed to create context");

if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), nullptr, MIUI_CRYPTO_KEY,
MIUI_CRYPTO_IV) != 1) {
EVP_CIPHER_CTX_free(ctx);
throw std::runtime_error("aes128cbcDecrypt: initialization error");
}

std::vector<unsigned char> plaintext(data.size() + AES_BLOCK_SIZE);
int outLen1 = 0, outLen2 = 0;

if (EVP_DecryptUpdate(ctx, plaintext.data(), &outLen1, data.data(),
static_cast<int>(data.size())) != 1) {
EVP_CIPHER_CTX_free(ctx);
throw std::runtime_error("aes128cbcDecrypt: decryption error (Update)");
}

if (EVP_DecryptFinal_ex(ctx, plaintext.data() + outLen1, &outLen2) != 1) {
EVP_CIPHER_CTX_free(ctx);
throw std::runtime_error(
"aes128cbcDecrypt: decryption error (Final) — invalid key/IV?");
}

EVP_CIPHER_CTX_free(ctx);
return std::string(plaintext.begin(), plaintext.begin() + outLen1 + outLen2);
}
Loading
Loading