Merge pull request #90 from gizmo98/patch-2
[libretro-ppsspp.git] / Core / Reporting.cpp
blob0c05d5eaa21b922d65bfa2a7cd88d7d128afe7c6
1 // Copyright (c) 2012- PPSSPP Project.
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // GNU General Public License 2.0 for more details.
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
18 #include "Core/Reporting.h"
20 #include "Common/CPUDetect.h"
21 #include "Core/CoreTiming.h"
22 #include "Core/Config.h"
23 #include "Core/CwCheat.h"
24 #include "Core/SaveState.h"
25 #include "Core/System.h"
26 #include "Core/FileSystems/MetaFileSystem.h"
27 #include "Core/HLE/sceDisplay.h"
28 #include "Core/HLE/sceKernelMemory.h"
29 #include "Core/ELF/ParamSFO.h"
30 #include "GPU/GPUInterface.h"
31 #include "GPU/GPUState.h"
32 #include "GPU/GLES/Framebuffer.h"
33 #include "net/http_client.h"
34 #include "net/resolve.h"
35 #include "net/url.h"
37 #include "base/stringutil.h"
38 #include "base/buffer.h"
39 #include "thread/thread.h"
40 #include "file/zip_read.h"
42 #include <set>
43 #include <stdlib.h>
44 #include <cstdarg>
46 namespace Reporting
48 const int DEFAULT_PORT = 80;
49 const u32 SPAM_LIMIT = 100;
50 const int PAYLOAD_BUFFER_SIZE = 100;
52 // Internal limiter on number of requests per instance.
53 static u32 spamProtectionCount = 0;
54 // Temporarily stores a reference to the hostname.
55 static std::string lastHostname;
56 // Keeps track of report-only-once identifiers. Since they're always constants, a pointer is okay.
57 static std::set<const char *> logOnceUsed;
58 // Keeps track of whether a harmful setting was ever used.
59 static bool everUnsupported = false;
60 // Support is cached here to avoid checking it on every single request.
61 static bool currentSupported = false;
63 enum RequestType
65 MESSAGE,
66 COMPAT,
69 struct Payload
71 RequestType type;
72 std::string string1;
73 std::string string2;
74 int int1;
75 int int2;
76 int int3;
78 static Payload payloadBuffer[PAYLOAD_BUFFER_SIZE];
79 static int payloadBufferPos = 0;
81 // Returns the full host (e.g. report.ppsspp.org:80.)
82 inline std::string ServerHost()
84 if (g_Config.sReportHost.compare("default") == 0)
85 return "";
86 return g_Config.sReportHost;
89 // Returns the length of the hostname part (e.g. before the :80.)
90 static size_t ServerHostnameLength()
92 if (!IsEnabled())
93 return g_Config.sReportHost.npos;
95 // IPv6 literal?
96 std::string hostString = ServerHost();
97 if (hostString[0] == '[')
99 size_t length = hostString.find("]:");
100 if (length != hostString.npos)
101 ++length;
102 return length;
104 else
105 return hostString.find(':');
108 // Returns only the hostname part (e.g. "report.ppsspp.org".)
109 static const char *ServerHostname()
111 if (!IsEnabled())
112 return NULL;
114 std::string host = ServerHost();
115 size_t length = ServerHostnameLength();
117 // This means there's no port number - it's already the hostname.
118 if (length == host.npos)
119 lastHostname = host;
120 else
121 lastHostname = host.substr(0, length);
122 return lastHostname.c_str();
125 // Returns only the port part (e.g. 80) as an int.
126 static int ServerPort()
128 if (!IsEnabled())
129 return 0;
131 std::string host = ServerHost();
132 size_t offset = ServerHostnameLength();
133 // If there's no port, use the default one.
134 if (offset == host.npos)
135 return DEFAULT_PORT;
137 // Skip the colon.
138 std::string port = host.substr(offset + 1);
139 return atoi(port.c_str());
142 // Should only be called once per request.
143 bool CheckSpamLimited()
145 return ++spamProtectionCount >= SPAM_LIMIT;
148 bool SendReportRequest(const char *uri, const std::string &data, const std::string &mimeType, Buffer *output = NULL)
150 bool result = false;
151 net::AutoInit netInit;
152 http::Client http;
153 Buffer theVoid;
155 if (output == NULL)
156 output = &theVoid;
158 if (http.Resolve(ServerHostname(), ServerPort()))
160 http.Connect();
161 http.POST(uri, data, mimeType, output);
162 http.Disconnect();
163 result = true;
166 return result;
169 std::string StripTrailingNull(const std::string &str)
171 size_t pos = str.find_first_of('\0');
172 if (pos != str.npos)
173 return str.substr(0, pos);
174 return str;
177 std::string GetPlatformIdentifer()
179 // TODO: Do we care about OS version?
180 #if defined(ANDROID)
181 return "Android";
182 #elif defined(_WIN64)
183 return "Windows 64";
184 #elif defined(_WIN32)
185 return "Windows";
186 #elif defined(IOS)
187 return "iOS";
188 #elif defined(__APPLE__)
189 return "Mac";
190 #elif defined(__SYMBIAN32__)
191 return "Symbian";
192 #elif defined(BLACKBERRY)
193 return "Blackberry";
194 #elif defined(LOONGSON)
195 return "Loongson";
196 #elif defined(MAEMO)
197 return "Nokia Maemo";
198 #elif defined(__linux__)
199 return "Linux";
200 #elif defined(__Bitrig__)
201 return "Bitrig";
202 #elif defined(__DragonFly__)
203 return "DragonFly";
204 #elif defined(__FreeBSD__)
205 return "FreeBSD";
206 #elif defined(__NetBSD__)
207 return "NetBSD";
208 #elif defined(__OpenBSD__)
209 return "OpenBSD";
210 #else
211 return "Unknown";
212 #endif
215 void Init()
217 // New game, clean slate.
218 spamProtectionCount = 0;
219 logOnceUsed.clear();
220 everUnsupported = false;
221 currentSupported = IsSupported();
224 void Shutdown()
226 // Just so it can be enabled in the menu again.
227 Init();
230 void DoState(PointerWrap &p)
232 const int LATEST_VERSION = 1;
233 auto s = p.Section("Reporting", 0, LATEST_VERSION);
234 if (!s || s < LATEST_VERSION) {
235 // Don't report from old savestates, they may "entomb" bugs.
236 everUnsupported = true;
237 return;
240 p.Do(everUnsupported);
243 void UpdateConfig()
245 currentSupported = IsSupported();
246 if (!currentSupported && PSP_IsInited())
247 everUnsupported = true;
250 bool ShouldLogOnce(const char *identifier)
252 // True if it wasn't there already -> so yes, log.
253 return logOnceUsed.insert(identifier).second;
256 void AddGameInfo(UrlEncoder &postdata)
258 // TODO: Maybe ParamSFOData shouldn't include nulls in std::strings? Don't work to break savedata, though...
259 postdata.Add("game", StripTrailingNull(g_paramSFO.GetValueString("DISC_ID")) + "_" + StripTrailingNull(g_paramSFO.GetValueString("DISC_VERSION")));
260 postdata.Add("game_title", StripTrailingNull(g_paramSFO.GetValueString("TITLE")));
261 postdata.Add("sdkver", sceKernelGetCompiledSdkVersion());
264 void AddSystemInfo(UrlEncoder &postdata)
266 std::string gpuPrimary, gpuFull;
267 if (gpu)
268 gpu->GetReportingInfo(gpuPrimary, gpuFull);
270 postdata.Add("version", PPSSPP_GIT_VERSION);
271 postdata.Add("gpu", gpuPrimary);
272 postdata.Add("gpu_full", gpuFull);
273 postdata.Add("cpu", cpu_info.Summarize());
274 postdata.Add("platform", GetPlatformIdentifer());
277 void AddConfigInfo(UrlEncoder &postdata)
279 postdata.Add("pixel_width", PSP_CoreParameter().pixelWidth);
280 postdata.Add("pixel_height", PSP_CoreParameter().pixelHeight);
282 g_Config.GetReportingInfo(postdata);
285 void AddGameplayInfo(UrlEncoder &postdata)
287 // Just to get an idea of how long they played.
288 postdata.Add("ticks", (const uint64_t)CoreTiming::GetTicks());
290 if (g_Config.iShowFPSCounter && g_Config.iShowFPSCounter < 4)
292 float vps, fps;
293 __DisplayGetAveragedFPS(&vps, &fps);
294 postdata.Add("vps", vps);
295 postdata.Add("fps", fps);
298 postdata.Add("savestate_used", SaveState::HasLoadedState());
301 int Process(int pos)
303 Payload &payload = payloadBuffer[pos];
305 UrlEncoder postdata;
306 AddSystemInfo(postdata);
307 AddGameInfo(postdata);
308 AddConfigInfo(postdata);
309 AddGameplayInfo(postdata);
311 switch (payload.type)
313 case MESSAGE:
314 postdata.Add("message", payload.string1);
315 postdata.Add("value", payload.string2);
316 payload.string1.clear();
317 payload.string2.clear();
319 postdata.Finish();
320 SendReportRequest("/report/message", postdata.ToString(), postdata.GetMimeType());
321 break;
323 case COMPAT:
324 postdata.Add("compat", payload.string1);
325 postdata.Add("graphics", StringFromFormat("%d", payload.int1));
326 postdata.Add("speed", StringFromFormat("%d", payload.int2));
327 postdata.Add("gameplay", StringFromFormat("%d", payload.int3));
328 payload.string1.clear();
330 postdata.Finish();
331 SendReportRequest("/report/compat", postdata.ToString(), postdata.GetMimeType());
332 break;
335 return 0;
338 bool IsSupported()
340 // Disabled when using certain hacks, because they make for poor reports.
341 if (g_Config.iRenderingMode >= FBO_READFBOMEMORY_MIN)
342 return false;
343 if (g_Config.bTimerHack)
344 return false;
345 if (CheatsInEffect())
346 return false;
347 // Not sure if we should support locked cpu at all, but definitely not far out values.
348 if (g_Config.iLockedCPUSpeed != 0 && (g_Config.iLockedCPUSpeed < 111 || g_Config.iLockedCPUSpeed > 333))
349 return false;
351 // Some users run the exe from a zip or something, and don't have fonts.
352 // This breaks things, but let's not report it since it's confusing.
353 #if defined(USING_WIN_UI) || defined(APPLE)
354 if (!File::Exists(g_Config.flash0Directory + "/font/jpn0.pgf"))
355 return false;
356 #else
357 FileInfo fo;
358 if (!VFSGetFileInfo("flash0/font/jpn0.pgf", &fo))
359 return false;
360 #endif
362 return !everUnsupported;
365 bool IsEnabled()
367 if (g_Config.sReportHost.empty() || (!currentSupported && PSP_IsInited()))
368 return false;
369 // Disabled by default for now.
370 if (g_Config.sReportHost.compare("default") == 0)
371 return false;
372 return true;
375 void Enable(bool flag, std::string host)
377 if (IsSupported() && IsEnabled() != flag)
379 // "" means explicitly disabled. Don't ever turn on by default.
380 // "default" means it's okay to turn it on by default.
381 g_Config.sReportHost = flag ? host : "";
385 void EnableDefault()
387 g_Config.sReportHost = "default";
390 void ReportMessage(const char *message, ...)
392 if (!IsEnabled() || CheckSpamLimited())
393 return;
395 const int MESSAGE_BUFFER_SIZE = 65536;
396 char temp[MESSAGE_BUFFER_SIZE];
398 va_list args;
399 va_start(args, message);
400 vsnprintf(temp, MESSAGE_BUFFER_SIZE - 1, message, args);
401 temp[MESSAGE_BUFFER_SIZE - 1] = '\0';
402 va_end(args);
404 int pos = payloadBufferPos++ % PAYLOAD_BUFFER_SIZE;
405 Payload &payload = payloadBuffer[pos];
406 payload.type = MESSAGE;
407 payload.string1 = message;
408 payload.string2 = temp;
410 std::thread th(Process, pos);
411 th.detach();
414 void ReportMessageFormatted(const char *message, const char *formatted)
416 if (!IsEnabled() || CheckSpamLimited())
417 return;
419 int pos = payloadBufferPos++ % PAYLOAD_BUFFER_SIZE;
420 Payload &payload = payloadBuffer[pos];
421 payload.type = MESSAGE;
422 payload.string1 = message;
423 payload.string2 = formatted;
425 std::thread th(Process, pos);
426 th.detach();
429 void ReportCompatibility(const char *compat, int graphics, int speed, int gameplay)
431 if (!IsEnabled())
432 return;
434 int pos = payloadBufferPos++ % PAYLOAD_BUFFER_SIZE;
435 Payload &payload = payloadBuffer[pos];
436 payload.type = COMPAT;
437 payload.string1 = compat;
438 payload.int1 = graphics;
439 payload.int2 = speed;
440 payload.int3 = gameplay;
442 std::thread th(Process, pos);
443 th.detach();