Files
etaHEN/Source Code/util/source/http.c
LightningMods 0fe0407b3a etaHEN 2.4B
etaHEN 2.4B

etaHEN 2.4B Change log

- Updated to support the latest PS5 Payload SDK
- Fixed etaHEN and Cheats support for 8.40-10.01
- Added a Game Overlay menu to show CPU/GPU Temp and utilization, Local IP Address and other future states
- Added a Kstuff menu for options like downloading the latest kstuff from github, turning off kstufff autoload and more
- Added a Custom Background Package Installer for installing PKGs from internal storage from any directory (Requires DPIv2 enabled for 5.50+)
- DPIv2 can now download local files via url example http://192.xxx.xxx.xxx:12800/data/etaHEN/etaHEN.log
- Improved Cheats support, cheats with or without 0 sections are now supported
- Added Fix by TheFlow to Improve 2.xx PS4 PKG speeds
- Replaced the donation links in the etaHEN credits menu with ones to github sponsers
- Removed the non-whitelist app jailbreak option and moved it to an optional Legacy CMD Server option in the etaHEN Settings off by default
- Game Decryptor has been updated for the Itemzflow Dumper
- Updated the Plugin loader System
- The Payload SDK ELFLDR is now REQUIRED for etaHEN to load
- Replaced HTTP2 with Curl for better compatibility
- Added timeout for ShellUI to receive a response (will stop it from freezing if no response is given)

small fix
2025-12-01 20:31:16 -05:00

612 lines
19 KiB
C

/* Copyright (C) 2025 etaHEN / LightningMods
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3, or (at your option) any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, see
<http://www.gnu.org/licenses/>. */
#include "common_utils.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdbool.h>
#include <libhttp2.h>
#include <minizip/unzip.h>
#include "../extern/tiny-json/tiny-json.hpp"
#define TEST_USER_AGENT "etaHEN_Downloader"
#include <curl/curl.h>
#define NET_HEAP_SIZE (32 * 1024)
#define MAX_CONCURRENT_REQUEST (4)
#define PRIVATE_CA_CERT_NUM (0)
#define COMMIT_HASH_FILE "/data/etaHEN/cheat_commit_hash.txt"
#define GITHUB_API_URL "https://api.github.com/repos/etaHEN/PS5_Cheats/commits"
uint64_t sceKernelGetProcessTime(void);
// Structure for download progress tracking
struct download_progress {
int fd;
uint64_t last_notify_time;
const uint64_t notify_interval;
const char* filename;
};
// Structure for JSON download
struct json_data {
char* data;
size_t size;
size_t capacity;
};
bool IniliatizeHTTP() {
CURLcode res = curl_global_init(CURL_GLOBAL_DEFAULT);
if (res != CURLE_OK) {
etaHEN_log("curl_global_init() error: %s", curl_easy_strerror(res));
return false;
}
etaHEN_log("cURL initialized successfully, version %s", curl_version());
return true;
}
// Callback function to write downloaded data to file
static size_t write_file_callback(void* contents, size_t size, size_t nmemb, void* userdata) {
struct download_progress* progress = (struct download_progress*)userdata;
size_t real_size = size * nmemb;
ssize_t written = sceKernelWrite(progress->fd, contents, real_size);
if (written != real_size) {
etaHEN_log("sceKernelWrite() error: written %ld, expected %zu", written, real_size);
return 0; // This will cause curl to abort
}
return real_size;
}
// Progress callback for file downloads
static int progress_callback(void* clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow) {
struct download_progress* progress = (struct download_progress*)clientp;
uint64_t current_time = sceKernelGetProcessTime();
// Check if we should notify
if (current_time - progress->last_notify_time >= progress->notify_interval) {
char notifyMsg[256];
float dlnow_mb = (float)dlnow / (1024 * 1024);
if (dltotal > 0) {
float dltotal_mb = (float)dltotal / (1024 * 1024);
int percent = (int)(((float)dlnow / dltotal) * 100);
snprintf(notifyMsg, sizeof(notifyMsg),
"Downloading the cheats repo:..\n%.1f/%.1f MB (%d%%)",
dlnow_mb, dltotal_mb, percent);
}
else {
snprintf(notifyMsg, sizeof(notifyMsg),
"Downloading the cheats repo...\n%.1f MB Downloaded",
dlnow_mb);
}
notify(true, notifyMsg);
progress->last_notify_time = current_time;
}
return 0; // Return 0 to continue
}
bool download_file(const char* url, const char* dst) {
CURL* curl;
CURLcode res;
bool success = false;
char notifyMsg[1000];
const char* filename = strrchr(dst, '/');
filename = filename ? filename + 1 : dst; // Get just the filename without the path
// Remove destination file if it exists
unlink(dst);
// Open file for writing
int fd = sceKernelOpen(dst, O_WRONLY | O_CREAT, 0777);
if (fd < 0) {
etaHEN_log("Failed to open destination file: %s (error: 0x%08X)", dst, fd);
return false;
}
// Initialize progress structure
struct download_progress progress = {
.fd = fd,
.last_notify_time = sceKernelGetProcessTime(),
.notify_interval = 6 * 1000000, // 6 seconds in microseconds
.filename = strrchr(dst, '/') ? strrchr(dst, '/') + 1 : dst
};
// Initialize curl
curl = curl_easy_init();
if (!curl) {
etaHEN_log("curl_easy_init() failed");
sceKernelClose(fd);
return false;
}
// Initial notification
etaHEN_log("Downloading %s to %s", url, dst);
// Set curl options
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &progress);
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback);
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &progress);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, TEST_USER_AGENT);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // Skip SSL verification
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 300L); // 5 minute timeout
// Perform the request
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
etaHEN_log("curl_easy_perform() failed: %s", curl_easy_strerror(res));
notify(true, "Failed to download the %s!\n\nCheck your internet connection and try again.\nError: %s", filename, curl_easy_strerror(res));
}
else {
// Check HTTP response code
long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
etaHEN_log("Response status code: %ld", response_code);
if (response_code == 200) {
// Get download size info
curl_off_t download_size;
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &download_size);
snprintf(notifyMsg, sizeof(notifyMsg),
"Successfully downloaded the %s\nTotal Size: %.1f MB", filename,
(float)download_size / (1024 * 1024));
notify(true, notifyMsg);
etaHEN_log("Download complete: %lld bytes", download_size);
success = true;
}
else {
etaHEN_log("HTTP error: unexpected status code %ld", response_code);
notify(true, "Failed to download the %s!\n\nServer returned an error.", filename);
}
}
// Cleanup
curl_easy_cleanup(curl);
sceKernelClose(fd);
return success;
}
// Callback function to write JSON data to memory
static size_t write_json_callback(void* contents, size_t size, size_t nmemb, void* userdata) {
struct json_data* json = (struct json_data*)userdata;
size_t real_size = size * nmemb;
// Resize buffer if needed
if (json->size + real_size >= json->capacity) {
size_t new_capacity = json->capacity * 2;
if (new_capacity < json->size + real_size + 1) {
new_capacity = json->size + real_size + 1;
}
char* new_data = realloc(json->data, new_capacity);
if (!new_data) {
etaHEN_log("Failed to reallocate memory for JSON data");
return 0; // This will cause curl to abort
}
json->data = new_data;
json->capacity = new_capacity;
}
// Copy data
memcpy(json->data + json->size, contents, real_size);
json->size += real_size;
json->data[json->size] = '\0'; // Null terminate
return real_size;
}
// Function to download JSON data from URL
static char* download_json(const char* url) {
CURL* curl;
CURLcode res;
char* result = NULL;
// Initialize JSON data structure
struct json_data json = {
.data = malloc(1024),
.size = 0,
.capacity = 1024
};
if (!json.data) {
etaHEN_log("Failed to allocate initial memory for JSON data");
return NULL;
}
json.data[0] = '\0';
// Initialize curl
curl = curl_easy_init();
if (!curl) {
etaHEN_log("curl_easy_init() failed");
free(json.data);
return NULL;
}
// Set curl options
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_json_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &json);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, TEST_USER_AGENT);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // Skip SSL verification
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // 30 second timeout
// Perform the request
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
etaHEN_log("curl_easy_perform() failed: %s", curl_easy_strerror(res));
free(json.data);
}
else {
// Check HTTP response code
long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
if (response_code == 200) {
etaHEN_log("Downloaded %zu bytes of JSON data", json.size);
result = json.data; // Return the data
}
else {
etaHEN_log("HTTP error: unexpected status code %ld", response_code);
free(json.data);
}
}
// Cleanup
curl_easy_cleanup(curl);
return result;
}
// Function to create directory if it doesn't exist
static void ensure_directory(const char* path) {
struct stat st = { 0 };
if (stat(path, &st) == -1) {
mkdir(path, 0755);
}
}
// Simple function to extract a zip file
bool extract_zip(const char* zip_path, const char* extract_dir) {
unzFile zip = unzOpen(zip_path);
if (!zip) {
etaHEN_log("Failed to open zip file: %s", zip_path);
notify(true, "Failed to open zip file");
return false;
}
ensure_directory(extract_dir);
// Go to the first file
if (unzGoToFirstFile(zip) != UNZ_OK) {
etaHEN_log("Empty zip file");
unzClose(zip);
notify(true, "Empty zip file");
return false;
}
// For notification timing
uint64_t last_notify_time = 0;
uint64_t current_time = 0;
const uint64_t notify_interval = 6 * 1000000; // 6 seconds in microseconds
// Count total files for progress reporting
int total_files = 0;
int processed_files = 0;
// First pass - count files
do {
total_files++;
} while (unzGoToNextFile(zip) == UNZ_OK);
// Reset to first file
unzGoToFirstFile(zip);
// Extract the zip filename for notifications
const char* zip_filename = strrchr(zip_path, '/');
zip_filename = zip_filename ? zip_filename + 1 : zip_path;
// Initial notification
char notifyMsg[256];
snprintf(notifyMsg, sizeof(notifyMsg), "Preparing to extract the cheats repo (%d files)", total_files);
notify(true, notifyMsg);
etaHEN_log("%s", notifyMsg);
// Get current time for notification timing
last_notify_time = sceKernelGetProcessTime();
char filename[512];
char full_path[1024];
int skip_root = 1; // Flag to skip the root GitHub folder
char root_folder[256] = { 0 };
int root_folder_len = 0;
// Get the root folder name (first entry)
unz_file_info file_info;
unzGetCurrentFileInfo(zip, &file_info, filename, sizeof(filename), NULL, 0, NULL, 0);
char* first_slash = strchr(filename, '/');
if (first_slash) {
root_folder_len = first_slash - filename + 1;
strncpy(root_folder, filename, root_folder_len);
root_folder[root_folder_len] = '\0';
etaHEN_log("Detected root folder: %s", root_folder);
}
// Reset to the first file
unzGoToFirstFile(zip);
do {
unzGetCurrentFileInfo(zip, &file_info, filename, sizeof(filename), NULL, 0, NULL, 0);
// Skip the root folder if needed
char* actual_filename = filename;
if (skip_root && root_folder_len > 0 && strncmp(filename, root_folder, root_folder_len) == 0) {
actual_filename = filename + root_folder_len;
if (strlen(actual_filename) == 0) {
// Skip empty names (the root directory entry)
continue;
}
}
// Create the output path
snprintf(full_path, sizeof(full_path), "%s/%s", extract_dir, actual_filename);
// Check if this is a directory
if (filename[strlen(filename) - 1] == '/') {
etaHEN_log("Creating directory: %s", full_path);
ensure_directory(full_path);
processed_files++;
continue;
}
// Create directories in the path
char* last_slash = strrchr(full_path, '/');
if (last_slash) {
*last_slash = '\0';
ensure_directory(full_path);
*last_slash = '/';
}
// Extract the file
if (unzOpenCurrentFile(zip) != UNZ_OK) {
etaHEN_log("Failed to open file in zip");
continue;
}
// Open with POSIX open() instead of fopen()
int out = open(full_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (out == -1) {
etaHEN_log("Failed to create output file: %s (error: %d)", full_path, errno);
unzCloseCurrentFile(zip);
continue;
}
char buffer[8192];
int bytes;
while ((bytes = unzReadCurrentFile(zip, buffer, sizeof(buffer))) > 0) {
write(out, buffer, bytes);
}
close(out);
unzCloseCurrentFile(zip);
// Increment processed files count
processed_files++;
// Check if it's time to show a notification
current_time = sceKernelGetProcessTime();
if (current_time - last_notify_time >= notify_interval) {
int progress_percent = (processed_files * 100) / total_files;
snprintf(notifyMsg, sizeof(notifyMsg),
"Extracting the cheats: %d/%d files (%d%%)",
processed_files, total_files, progress_percent);
notify(true, notifyMsg);
etaHEN_log("%s", notifyMsg);
last_notify_time = current_time;
}
} while (unzGoToNextFile(zip) == UNZ_OK);
// Final notification
snprintf(notifyMsg, sizeof(notifyMsg),
"Cheats Extraction complete (%d files)",
processed_files);
notify(true, notifyMsg);
etaHEN_log("%s", notifyMsg);
unzClose(zip);
return true;
}
// Function to extract SHA from JSON response
static bool extract_commit_sha(const char* json_data, char* sha_buffer, size_t buffer_size) {
if (!json_data || !sha_buffer) {
return false;
}
etaHEN_log("json_data %s", json_data);
// Look for the first "sha" field in the JSON
const char* sha_start = strstr(json_data, "\"sha\":");
if (!sha_start) {
etaHEN_log("Could not find 'sha' field in JSON response");
return false;
}
// Move past "sha":
sha_start += 6;
// Skip whitespace and find the opening quote
while (*sha_start == ' ' || *sha_start == '\t') {
sha_start++;
}
if (*sha_start != '"') {
etaHEN_log("Invalid JSON format - expected quote after 'sha':");
return false;
}
sha_start++; // Skip the opening quote
// Find the closing quote
const char* sha_end = strchr(sha_start, '"');
if (!sha_end) {
etaHEN_log("Invalid JSON format - no closing quote for sha value");
return false;
}
// Calculate length and copy
size_t sha_length = sha_end - sha_start;
if (sha_length >= buffer_size) {
etaHEN_log("SHA too long for buffer");
return false;
}
strncpy(sha_buffer, sha_start, sha_length);
sha_buffer[sha_length] = '\0';
etaHEN_log("Extracted commit SHA: %s", sha_buffer);
return true;
}
// Function to read stored commit hash from file
static bool read_stored_commit_hash(char* hash_buffer, size_t buffer_size) {
int fd = sceKernelOpen(COMMIT_HASH_FILE, O_RDONLY, 0);
if (fd < 0) {
etaHEN_log("No stored commit hash file found");
return false;
}
int read_bytes = sceKernelRead(fd, hash_buffer, buffer_size - 1);
sceKernelClose(fd);
if (read_bytes <= 0) {
etaHEN_log("Failed to read stored commit hash");
return false;
}
hash_buffer[read_bytes] = '\0';
// Remove any trailing newline or whitespace
char* end = hash_buffer + strlen(hash_buffer) - 1;
while (end > hash_buffer && (*end == '\n' || *end == '\r' || *end == ' ')) {
*end = '\0';
end--;
}
etaHEN_log("Read stored commit hash: %s", hash_buffer);
return true;
}
// Function to write commit hash to file
static bool write_commit_hash(const char* hash) {
int fd = sceKernelOpen(COMMIT_HASH_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
etaHEN_log("Failed to open commit hash file for writing: 0x%08X", fd);
return false;
}
int written = sceKernelWrite(fd, hash, strlen(hash));
sceKernelClose(fd);
if (written != (int)strlen(hash)) {
etaHEN_log("Failed to write commit hash to file");
return false;
}
etaHEN_log("Stored new commit hash: %s", hash);
return true;
}
// Main function to check for new commits
bool check_for_new_commit() {
char* json_data = NULL;
char latest_commit[64] = {0};
char stored_commit[64] = {0};
bool has_new_commit = false;
etaHEN_log("Checking for new commits...");
notify(true, "Checking for updates to the cheats repo...");
// Download the latest commit information
json_data = download_json(GITHUB_API_URL);
if (!json_data) {
etaHEN_log("Failed to download commit information from GitHub API");
notify(true, "Failed to check the cheats repo for updates\nCheck your Connection and try again");
return false;
}
// Extract the latest commit SHA
if (!extract_commit_sha(json_data, latest_commit, sizeof(latest_commit))) {
etaHEN_log("Failed to extract commit SHA from JSON response");
notify(true, "Failed to parse update information\nUsing existing cheats repo");
free(json_data);
return false;
}
// Read the stored commit hash
bool has_stored_hash = read_stored_commit_hash(stored_commit, sizeof(stored_commit));
if (!has_stored_hash) {
etaHEN_log("No stored commit hash - treating as new commit");
has_new_commit = true;
} else {
// Compare the commits
if (strcmp(latest_commit, stored_commit) != 0) {
etaHEN_log("New commit detected: %s (was: %s)", latest_commit, stored_commit);
has_new_commit = true;
} else {
etaHEN_log("No new commits - repo is up to date");
has_new_commit = false;
}
}
// If there's a new commit, store the new hash
if (has_new_commit) {
if (!write_commit_hash(latest_commit)) {
etaHEN_log("Warning: Failed to store new commit hash");
}
notify(true, "New cheats update found!\nDownloading latest version...");
} else {
notify(true, "Cheats repo is up to date!");
}
free(json_data);
return has_new_commit;
}