Files
ppsspp/Core/HLE/sceParseHttp.cpp
T
Henrik RydgårdandClaude Opus 5.5 7dd1475ed7 HLE: Pass the MIPSState to syscalls and their implementations
HLEFunc and ReplaceFunc now take the MIPSState they run on. The dispatch
paths (both interpreters and every JIT backend) pass in the state they
belong to, and CallSyscall*, hleFinishSyscall and the HLE mips call
machinery use it instead of currentMIPS.

The wrappers in FunctionWrappers.h take the function as `auto` and call it
through HLECall, which also accepts an implementation with an extra leading
MIPSState * parameter and passes the state to it. Any other signature still
fails to compile, as before. PARAM/RETURN now refer to a local `mips`.

The HLE functions (and replacement functions) that used currentMIPS now take
the state that way. Callers outside a syscall's own frame - CoreTiming
events, the adhoc matching thread, dialogs, PPGe, the debugger UIs - pass
currentMIPS or &mipsr4k explicitly. The kernel's thread internals still use
currentMIPS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 15:45:14 +02:00

177 lines
7.1 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// 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, version 2.0 or later versions.
// 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <sstream>
#include <regex>
#include "Core/HLE/HLE.h"
#include "Core/HLE/sceHttp.h"
#include "Core/HLE/sceParseHttp.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/Debugger/MemBlockInfo.h"
#include "Common/StringUtils.h"
std::string getHeaderString(std::istringstream &headers) {
std::string line = "";
while (headers.rdbuf()->in_avail()) {
char c = headers.get();
if (c == '\n' || c == '\r') {
break;
}
line.push_back(c);
}
return line;
}
// FIXME: Is it allowed for fieldName to be null/0 or empty string ? JPCSP seems to ignore it
static int sceParseHttpResponseHeader(u32 headerAddr, int headerLength, const char *fieldName, u32 valueAddr, u32 valueLengthAddr) {
if (!Memory::IsValidRange(headerAddr, headerLength))
return hleLogError(Log::sceNet, -1, "invalid arg");
if (!Memory::IsValidRange(valueLengthAddr, 4))
return hleLogError(Log::sceNet, -1, "invalid arg");
// FIXME: Not sure whether valueAddr can be null or not (in case the game just need to get the size to allocate the value buffer first)
// Note: Based on the outputted value address from JPCSP, the address seems to be within the input headers address range, thus no need to allocate output value buffer
if (!Memory::IsValidRange(valueAddr, Memory::ReadUnchecked_U32(valueLengthAddr)))
return hleLogError(Log::sceNet, -1, "invalid arg");
std::string field = fieldName ? fieldName : "";
field = StripSpaces(field);
std::string headers(Memory::GetCharPointer(headerAddr), headerLength);
std::istringstream hdrs(headers);
u32 addr = 0;
u32 len = 0;
bool found = false;
while (hdrs.rdbuf()->in_avail()) {
std::string headerString = getHeaderString(hdrs);
const std::string delim = ":"; // JPCSP use ": " delimiter
size_t delimpos = headerString.find(delim);
std::string key = headerString.substr(0, delimpos); // the key part
if (equalsNoCase(StripSpaces(key), field)) {
found = true;
int offset = hdrs.tellg();
if (offset >= 0) {
offset -= (int)headerString.length();
offset--; // counting the excluded LF/CR
}
else {
offset = headerLength - (int)headerString.length();
}
addr = headerAddr + offset + (int)(delimpos != std::string::npos? delimpos + delim.length() : headerString.length()); // value address within the headers
headerString.erase(0, delimpos + delim.length()); // the value part
size_t valpos = headerString.find_first_not_of(" "); // excludes the leading space (if any)
if (valpos == std::string::npos) {
len = 0;
}
else {
len = (int)headerString.length() - (int)valpos;
addr += (int)valpos;
}
Memory::WriteUnchecked_U32(addr, valueAddr);
NotifyMemInfo(MemBlockFlags::WRITE, valueAddr, 4, "sceParseHttpResponseHeader");
Memory::WriteUnchecked_U32(len, valueLengthAddr);
NotifyMemInfo(MemBlockFlags::WRITE, valueLengthAddr, 4, "sceParseHttpResponseHeader");
break;
}
}
if (!found) {
Memory::WriteUnchecked_U32(0, valueAddr);
NotifyMemInfo(MemBlockFlags::WRITE, valueAddr, 4, "sceParseHttpResponseHeader");
Memory::WriteUnchecked_U32(0, valueLengthAddr);
NotifyMemInfo(MemBlockFlags::WRITE, valueLengthAddr, 4, "sceParseHttpResponseHeader");
return hleLogError(Log::sceNet, SCE_HTTP_ERROR_PARSE_HTTP_NOT_FOUND, "parse http not found");
}
return hleLogDebug(Log::sceNet, len);
}
static int sceParseHttpStatusLine(MIPSState *mips, u32 headerAddr, u32 headerLength, u32 httpVersionMajorAddr, u32 httpVersionMinorAddr, u32 httpStatusCodeAddr, u32 httpStatusCommentAddr, u32 httpStatusCommentLengthAddr) {
WARN_LOG(Log::sceNet, "UNTESTED sceParseHttpStatusLine(%x, %d, %x, %x, %x, %x, %x) at %08x", headerAddr, headerLength, httpVersionMajorAddr, httpVersionMinorAddr, httpStatusCodeAddr, httpStatusCommentAddr, httpStatusCommentLengthAddr, mips->pc);
if (!Memory::IsValidRange(headerAddr, headerLength))
return hleLogError(Log::sceNet, -1, "invalid arg");
std::string headers(Memory::GetCharPointer(headerAddr), headerLength);
// Get first line from header, line ending can be '\n' or '\r'
std::istringstream hdr(headers);
std::string line;
std::getline(hdr, line, '\n');
hdr.str(line);
std::getline(hdr, line, '\r');
NotifyMemInfo(MemBlockFlags::READ, headerAddr, (u32)line.size(), "ParseHttpStatusLine");
DEBUG_LOG(Log::sceNet, "Headers: %s", headers.c_str());
// Split the string by pattern, based on JPCSP
std::regex rgx("HTTP/(\\d)\\.(\\d)\\s+(\\d+)(.*)");
std::smatch matches;
if (!std::regex_search(line, matches, rgx))
return hleLogError(Log::sceNet, -1, "invalid arg"); // SCE_HTTP_ERROR_PARSE_HTTP_NOT_FOUND
try {
// Convert and Store the value
if (Memory::IsValidRange(httpVersionMajorAddr, 4)) {
Memory::WriteUnchecked_U32(stoi(matches[1].str()), httpVersionMajorAddr);
NotifyMemInfo(MemBlockFlags::WRITE, httpVersionMajorAddr, 4, "ParseHttpStatusLine");
}
if (Memory::IsValidRange(httpVersionMinorAddr, 4)) {
Memory::WriteUnchecked_U32(stoi(matches[2].str()), httpVersionMinorAddr);
NotifyMemInfo(MemBlockFlags::WRITE, httpVersionMinorAddr, 4, "ParseHttpStatusLine");
}
if (Memory::IsValidRange(httpStatusCodeAddr, 4)) {
Memory::WriteUnchecked_U32(stoi(matches[3].str()), httpStatusCodeAddr);
NotifyMemInfo(MemBlockFlags::WRITE, httpStatusCodeAddr, 4, "ParseHttpStatusLine");
}
std::string sc = matches[4].str();
if (Memory::IsValidRange(httpStatusCommentAddr, 4)) {
Memory::WriteUnchecked_U32(headerAddr + (int)headers.find(sc), httpStatusCommentAddr);
NotifyMemInfo(MemBlockFlags::WRITE, httpStatusCommentAddr, 4, "ParseHttpStatusLine");
}
if (Memory::IsValidRange(httpStatusCommentLengthAddr, 4)) {
Memory::WriteUnchecked_U32((u32)sc.size(), httpStatusCommentLengthAddr);
NotifyMemInfo(MemBlockFlags::WRITE, httpStatusCommentLengthAddr, 4, "ParseHttpStatusLine");
}
}
catch (const std::runtime_error& ex) {
return hleLogError(Log::sceNet, -1, "Runtime error: %s", ex.what());
}
catch (const std::exception& ex) {
return hleLogError(Log::sceNet, -1, "Error occurred: %s", ex.what()); // SCE_HTTP_ERROR_PARSE_HTTP_INVALID_VALUE
}
catch (...) {
return hleLogError(Log::sceNet, -1, "Unknown error");
}
return hleLogDebug(Log::sceNet, 0);
}
const HLEFunction sceParseHttp[] = {
{0X8077A433, &WrapI_UUUUUUU<sceParseHttpStatusLine>, "sceParseHttpStatusLine", 'i', "xxxxxxx"},
{0XAD7BFDEF, &WrapI_UICUU<sceParseHttpResponseHeader>, "sceParseHttpResponseHeader", 'i', "xisxx" },
};
void Register_sceParseHttp() {
RegisterHLEModule("sceParseHttp", ARRAY_SIZE(sceParseHttp), sceParseHttp);
}