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"
37 #include "base/stringutil.h"
38 #include "base/buffer.h"
39 #include "thread/thread.h"
40 #include "file/zip_read.h"
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;
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)
86 return g_Config
.sReportHost
;
89 // Returns the length of the hostname part (e.g. before the :80.)
90 static size_t ServerHostnameLength()
93 return g_Config
.sReportHost
.npos
;
96 std::string hostString
= ServerHost();
97 if (hostString
[0] == '[')
99 size_t length
= hostString
.find("]:");
100 if (length
!= hostString
.npos
)
105 return hostString
.find(':');
108 // Returns only the hostname part (e.g. "report.ppsspp.org".)
109 static const char *ServerHostname()
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
)
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()
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
)
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
)
151 net::AutoInit netInit
;
158 if (http
.Resolve(ServerHostname(), ServerPort()))
161 http
.POST(uri
, data
, mimeType
, output
);
169 std::string
StripTrailingNull(const std::string
&str
)
171 size_t pos
= str
.find_first_of('\0');
173 return str
.substr(0, pos
);
177 std::string
GetPlatformIdentifer()
179 // TODO: Do we care about OS version?
182 #elif defined(_WIN64)
184 #elif defined(_WIN32)
188 #elif defined(__APPLE__)
190 #elif defined(__SYMBIAN32__)
192 #elif defined(BLACKBERRY)
194 #elif defined(LOONGSON)
197 return "Nokia Maemo";
198 #elif defined(__linux__)
200 #elif defined(__Bitrig__)
202 #elif defined(__DragonFly__)
204 #elif defined(__FreeBSD__)
206 #elif defined(__NetBSD__)
208 #elif defined(__OpenBSD__)
217 // New game, clean slate.
218 spamProtectionCount
= 0;
220 everUnsupported
= false;
221 currentSupported
= IsSupported();
226 // Just so it can be enabled in the menu again.
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;
240 p
.Do(everUnsupported
);
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
;
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)
293 __DisplayGetAveragedFPS(&vps
, &fps
);
294 postdata
.Add("vps", vps
);
295 postdata
.Add("fps", fps
);
298 postdata
.Add("savestate_used", SaveState::HasLoadedState());
303 Payload
&payload
= payloadBuffer
[pos
];
306 AddSystemInfo(postdata
);
307 AddGameInfo(postdata
);
308 AddConfigInfo(postdata
);
309 AddGameplayInfo(postdata
);
311 switch (payload
.type
)
314 postdata
.Add("message", payload
.string1
);
315 postdata
.Add("value", payload
.string2
);
316 payload
.string1
.clear();
317 payload
.string2
.clear();
320 SendReportRequest("/report/message", postdata
.ToString(), postdata
.GetMimeType());
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();
331 SendReportRequest("/report/compat", postdata
.ToString(), postdata
.GetMimeType());
340 // Disabled when using certain hacks, because they make for poor reports.
341 if (g_Config
.iRenderingMode
>= FBO_READFBOMEMORY_MIN
)
343 if (g_Config
.bTimerHack
)
345 if (CheatsInEffect())
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))
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"))
358 if (!VFSGetFileInfo("flash0/font/jpn0.pgf", &fo
))
362 return !everUnsupported
;
367 if (g_Config
.sReportHost
.empty() || (!currentSupported
&& PSP_IsInited()))
369 // Disabled by default for now.
370 if (g_Config
.sReportHost
.compare("default") == 0)
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
: "";
387 g_Config
.sReportHost
= "default";
390 void ReportMessage(const char *message
, ...)
392 if (!IsEnabled() || CheckSpamLimited())
395 const int MESSAGE_BUFFER_SIZE
= 65536;
396 char temp
[MESSAGE_BUFFER_SIZE
];
399 va_start(args
, message
);
400 vsnprintf(temp
, MESSAGE_BUFFER_SIZE
- 1, message
, args
);
401 temp
[MESSAGE_BUFFER_SIZE
- 1] = '\0';
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
);
414 void ReportMessageFormatted(const char *message
, const char *formatted
)
416 if (!IsEnabled() || CheckSpamLimited())
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
);
429 void ReportCompatibility(const char *compat
, int graphics
, int speed
, int gameplay
)
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
);