Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/Dialog/PSPMsgDialog.cpp
5654 views
1
// Copyright (c) 2012- PPSSPP Project.
2
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.
6
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.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include <algorithm>
19
20
#include "Common/Serialize/Serializer.h"
21
#include "Common/Serialize/SerializeFuncs.h"
22
#include "Common/StringUtils.h"
23
#include "Core/Dialog/PSPMsgDialog.h"
24
#include "Core/Dialog/PSPSaveDialog.h"
25
#include "Core/Util/PPGeDraw.h"
26
#include "Core/HLE/ErrorCodes.h"
27
#include "Core/HLE/sceCtrl.h"
28
#include "Core/MemMapHelpers.h"
29
#include "Core/Reporting.h"
30
#include "Core/Compatibility.h"
31
#include "Core/System.h"
32
#include "Common/Data/Text/I18n.h"
33
#include "Common/Data/Encoding/Utf8.h"
34
35
static const float FONT_SCALE = 0.65f;
36
37
// These are rough, it seems to take a long time to init, and probably depends on threads.
38
// TODO: This takes like 700ms on a PSP but that's annoyingly long.
39
const static int MSG_INIT_DELAY_US = 300000;
40
const static int MSG_SHUTDOWN_DELAY_US = 26000;
41
42
PSPMsgDialog::PSPMsgDialog(UtilityDialogType type) : PSPDialog(type) {
43
}
44
45
PSPMsgDialog::~PSPMsgDialog() {
46
}
47
48
bool PSPMsgDialog::UseAutoStatus() {
49
return PSP_CoreParameter().compat.flags().MsgDialogAutoStatus;
50
}
51
52
int PSPMsgDialog::Init(unsigned int paramAddr) {
53
// Ignore if already running
54
if (GetStatus() != SCE_UTILITY_STATUS_NONE) {
55
ERROR_LOG_REPORT(Log::sceUtility, "sceUtilityMsgDialogInitStart: invalid status");
56
return SCE_ERROR_UTILITY_INVALID_STATUS;
57
}
58
59
messageDialogAddr = paramAddr;
60
if (!Memory::IsValidAddress(messageDialogAddr))
61
{
62
return 0;
63
}
64
int size = Memory::Read_U32(paramAddr);
65
memset(&messageDialog,0,sizeof(messageDialog));
66
// Only copy the right size to support different request format
67
Memory::Memcpy(&messageDialog,paramAddr,size);
68
69
// debug info
70
int optionsNotCoded = messageDialog.options & ~SCE_UTILITY_MSGDIALOG_OPTION_SUPPORTED;
71
if(optionsNotCoded)
72
{
73
ERROR_LOG_REPORT(Log::sceUtility, "PSPMsgDialog options not coded : 0x%08x", optionsNotCoded);
74
}
75
76
flag = 0;
77
scrollPos_ = 0.0f;
78
framesUpHeld_ = 0;
79
framesDownHeld_ = 0;
80
81
// Check request invalidity
82
if(messageDialog.type == 0 && !(messageDialog.errorNum & 0x80000000))
83
{
84
flag |= DS_ERROR;
85
messageDialog.result = SCE_ERROR_UTILITY_MSGDIALOG_ERRORCODEINVALID;
86
}
87
else if(size == SCE_UTILITY_MSGDIALOG_SIZE_V2 && messageDialog.type == 1)
88
{
89
unsigned int validOp = SCE_UTILITY_MSGDIALOG_OPTION_TEXTSOUND |
90
SCE_UTILITY_MSGDIALOG_OPTION_YESNO |
91
SCE_UTILITY_MSGDIALOG_OPTION_DEFAULT_NO;
92
if (((messageDialog.options | validOp) ^ validOp) != 0)
93
{
94
flag |= DS_ERROR;
95
messageDialog.result = SCE_ERROR_UTILITY_MSGDIALOG_BADOPTION;
96
}
97
}
98
else if(size == SCE_UTILITY_MSGDIALOG_SIZE_V3)
99
{
100
if((messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_DEFAULT_NO) &&
101
!(messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_YESNO))
102
{
103
flag |= DS_ERROR;
104
messageDialog.result = SCE_ERROR_UTILITY_MSGDIALOG_BADOPTION;
105
}
106
if (messageDialog.options & ~SCE_UTILITY_MSGDIALOG_OPTION_SUPPORTED)
107
{
108
flag |= DS_ERROR;
109
messageDialog.result = SCE_ERROR_UTILITY_MSGDIALOG_BADOPTION;
110
}
111
}
112
113
if(flag == 0)
114
{
115
yesnoChoice = 1;
116
if(messageDialog.type == 1)
117
flag |= DS_MSG;
118
if(messageDialog.type == 0)
119
flag |= DS_ERRORMSG;
120
if((messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_YESNO) &&
121
((size == SCE_UTILITY_MSGDIALOG_SIZE_V3) ||
122
(size == SCE_UTILITY_MSGDIALOG_SIZE_V2 && messageDialog.type == 1)))
123
flag |= DS_YESNO;
124
if(messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_DEFAULT_NO)
125
{
126
yesnoChoice = 0;
127
flag |= DS_DEFNO;
128
}
129
if((messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_OK) && (size == SCE_UTILITY_MSGDIALOG_SIZE_V3))
130
{
131
yesnoChoice = 1;
132
flag |= DS_OK;
133
}
134
if((flag & DS_YESNO) || (flag & DS_OK))
135
flag |= DS_VALIDBUTTON;
136
if(!((messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_NOCANCEL) && (size == SCE_UTILITY_MSGDIALOG_SIZE_V3)))
137
flag |= DS_CANCELBUTTON;
138
if(messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_NOSOUND)
139
flag |= DS_NOSOUND;
140
}
141
142
if (flag & DS_ERRORMSG) {
143
FormatErrorCode(messageDialog.errorNum);
144
} else {
145
truncate_cpy(msgText, messageDialog.string);
146
}
147
148
ChangeStatusInit(MSG_INIT_DELAY_US);
149
150
UpdateButtons();
151
InitCommon();
152
StartFade(true);
153
return 0;
154
}
155
156
157
void PSPMsgDialog::FormatErrorCode(uint32_t code) {
158
auto err = GetI18NCategory(I18NCat::DIALOG);
159
160
switch (code) {
161
case SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN:
162
snprintf(msgText, 512, "%s (%08x)", err->T_cstr("MsgErrorSavedataDataBroken", "Save data was corrupt."), code);
163
break;
164
165
case SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_MS:
166
case SCE_UTILITY_SAVEDATA_ERROR_RW_NO_MEMSTICK:
167
case SCE_UTILITY_SAVEDATA_ERROR_SAVE_NO_MS:
168
case SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_MS:
169
case SCE_UTILITY_SAVEDATA_ERROR_SIZES_NO_MS:
170
snprintf(msgText, 512, "%s (%08x)", err->T_cstr("MsgErrorSavedataNoMS", "Memory stick not inserted."), code);
171
break;
172
173
case SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA:
174
case SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA:
175
case SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_DATA:
176
case SCE_UTILITY_SAVEDATA_ERROR_SIZES_NO_DATA:
177
snprintf(msgText, 512, "%s (%08x)", err->T_cstr("MsgErrorSavedataNoData", "Warning: no save data was found."), code);
178
break;
179
180
case SCE_UTILITY_SAVEDATA_ERROR_RW_MEMSTICK_FULL:
181
case SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE:
182
snprintf(msgText, 512, "%s (%08x)", err->T_cstr("MsgErrorSavedataMSFull", "Memory stick full. Check your storage space."), code);
183
break;
184
185
default:
186
snprintf(msgText, 512, "%s %08x", err->T_cstr("MsgErrorCode", "Error code:"), code);
187
}
188
}
189
190
void PSPMsgDialog::DisplayMessage(const std::string &text, bool hasYesNo, bool hasOK) {
191
auto di = GetI18NCategory(I18NCat::DIALOG);
192
193
PPGeStyle buttonStyle = FadedStyle(PPGeAlign::BOX_CENTER, FONT_SCALE);
194
PPGeStyle messageStyle = FadedStyle(PPGeAlign::BOX_HCENTER, FONT_SCALE);
195
196
// Without the scrollbar, we have 390 total pixels.
197
float WRAP_WIDTH = 340.0f;
198
if ((size_t)UTF8StringNonASCIICount(text) >= text.size() / 4) {
199
WRAP_WIDTH = 376.0f;
200
if (text.size() > 12) {
201
messageStyle.scale = 0.6f;
202
}
203
}
204
205
float totalHeight = 0.0f;
206
PPGeMeasureText(nullptr, &totalHeight, text, FONT_SCALE, PPGE_LINE_WRAP_WORD, WRAP_WIDTH);
207
// The PSP normally only shows about 8 lines at a time.
208
// For improved UX, we intentionally show part of the next line.
209
float visibleHeight = std::min(totalHeight, 175.0f);
210
float h2 = visibleHeight / 2.0f;
211
212
float centerY = 135.0f;
213
float sy = centerY - h2 - 15.0f;
214
float ey = centerY + h2 + 20.0f;
215
float buttonY = centerY + h2 + 5.0f;
216
217
auto drawSelectionBoxAndAdjust = [&](float x) {
218
// Box has a fixed size.
219
float w = 15.0f;
220
float h = 8.0f;
221
PPGeDrawRect(x - w, buttonY - h, x + w, buttonY + h, CalcFadedColor(0x6DCFCFCF));
222
223
centerY -= h + 5.0f;
224
sy -= h + 5.0f;
225
ey = buttonY + h * 2.0f + 5.0f;
226
};
227
228
if (hasYesNo) {
229
if (yesnoChoice == 1) {
230
drawSelectionBoxAndAdjust(204.0f);
231
} else {
232
drawSelectionBoxAndAdjust(273.0f);
233
}
234
235
PPGeDrawText(di->T("Yes"), 203.0f, buttonY - 1.0f, buttonStyle);
236
PPGeDrawText(di->T("No"), 272.0f, buttonY - 1.0f, buttonStyle);
237
if (IsButtonPressed(CTRL_LEFT) && yesnoChoice == 0) {
238
yesnoChoice = 1;
239
} else if (IsButtonPressed(CTRL_RIGHT) && yesnoChoice == 1) {
240
yesnoChoice = 0;
241
}
242
buttonY += 8.0f + 5.0f;
243
}
244
245
if (hasOK) {
246
drawSelectionBoxAndAdjust(240.0f);
247
248
PPGeDrawText(di->T("OK"), 239.0f, buttonY - 1.0f, buttonStyle);
249
buttonY += 8.0f + 5.0f;
250
}
251
252
PPGeScissor(0, (int)(centerY - h2 - 2), 480, (int)(centerY + h2 + 2));
253
PPGeDrawTextWrapped(text.c_str(), 240.0f, centerY - h2 - scrollPos_, WRAP_WIDTH, 0, messageStyle);
254
PPGeScissorReset();
255
256
// Do we need a scrollbar?
257
if (visibleHeight < totalHeight) {
258
float scrollSpeed = 5.0f;
259
float scrollMax = totalHeight - visibleHeight;
260
261
float bobHeight = (visibleHeight / totalHeight) * visibleHeight;
262
float bobOffset = (scrollPos_ / scrollMax) * (visibleHeight - bobHeight);
263
float bobY1 = centerY - h2 + bobOffset;
264
PPGeDrawRect(435.0f, bobY1, 440.0f, bobY1 + bobHeight, CalcFadedColor(0xFFCCCCCC));
265
266
auto buttonDown = [this](int btn, int &held) {
267
if (IsButtonPressed(btn)) {
268
held = 0;
269
return true;
270
}
271
return IsButtonHeld(btn, held, 1, 1);
272
};
273
if (buttonDown(CTRL_DOWN, framesDownHeld_) && scrollPos_ < scrollMax) {
274
scrollPos_ = std::min(scrollMax, scrollPos_ + scrollSpeed);
275
}
276
if (buttonDown(CTRL_UP, framesUpHeld_) && scrollPos_ > 0.0f) {
277
scrollPos_ = std::max(0.0f, scrollPos_ - scrollSpeed);
278
}
279
}
280
281
PPGeDrawRect(40.0f, sy, 440.0f, sy + 1.0f, CalcFadedColor(0xFFFFFFFF));
282
PPGeDrawRect(40.0f, ey, 440.0f, ey + 1.0f, CalcFadedColor(0xFFFFFFFF));
283
}
284
285
int PSPMsgDialog::Update(int animSpeed) {
286
if (GetStatus() != SCE_UTILITY_STATUS_RUNNING) {
287
return SCE_ERROR_UTILITY_INVALID_STATUS;
288
}
289
290
if (flag & (DS_ERROR | DS_ABORT)) {
291
ChangeStatus(SCE_UTILITY_STATUS_FINISHED, 0);
292
} else {
293
UpdateButtons();
294
UpdateCommon();
295
UpdateFade(animSpeed);
296
297
StartDraw();
298
// white -> RGB(168,173,189), black -> RGB(129,134,150)
299
// (255 - a) + (x * a / 255) = 173, x * a / 255 = 134
300
// a = 255 - w + b = 158, x = b * 255 / a = ?
301
// but is not drawn using x * a + y * (255 - a) here?
302
//PPGeDrawRect(0, 0, 480, 272, CalcFadedColor(0x9EF2D8D0));
303
PPGeDrawRect(0, 0, 480, 272, CalcFadedColor(0xC0C8B2AC));
304
305
if ((flag & DS_MSG) || (flag & DS_ERRORMSG))
306
DisplayMessage(msgText, (flag & DS_YESNO) != 0, (flag & DS_OK) != 0);
307
308
if (flag & (DS_OK | DS_VALIDBUTTON))
309
DisplayButtons(DS_BUTTON_OK, messageDialog.common.size == SCE_UTILITY_MSGDIALOG_SIZE_V3 ? messageDialog.okayButton : "");
310
311
if (flag & DS_CANCELBUTTON)
312
DisplayButtons(DS_BUTTON_CANCEL, messageDialog.common.size == SCE_UTILITY_MSGDIALOG_SIZE_V3 ? messageDialog.cancelButton : "");
313
314
if (IsButtonPressed(cancelButtonFlag) && (flag & DS_CANCELBUTTON))
315
{
316
if(messageDialog.common.size == SCE_UTILITY_MSGDIALOG_SIZE_V3 ||
317
((messageDialog.common.size == SCE_UTILITY_MSGDIALOG_SIZE_V2) && (flag & DS_YESNO)))
318
messageDialog.buttonPressed = 3;
319
else
320
messageDialog.buttonPressed = 0;
321
StartFade(false);
322
}
323
else if (IsButtonPressed(okButtonFlag) && (flag & DS_VALIDBUTTON))
324
{
325
if (yesnoChoice == 0)
326
{
327
messageDialog.buttonPressed = 2;
328
}
329
else
330
{
331
messageDialog.buttonPressed = 1;
332
}
333
StartFade(false);
334
}
335
336
337
EndDraw();
338
339
messageDialog.result = 0;
340
}
341
342
Memory::Memcpy(messageDialogAddr, &messageDialog, messageDialog.common.size, "MsgDialogParam");
343
return 0;
344
}
345
346
int PSPMsgDialog::Abort() {
347
// Katekyoushi Hitman Reborn! Battle Arena expects this to fail when not running.
348
if (GetStatus() != SCE_UTILITY_STATUS_RUNNING) {
349
return SCE_ERROR_UTILITY_INVALID_STATUS;
350
} else {
351
// Status is not actually changed until Update().
352
flag |= DS_ABORT;
353
return 0;
354
}
355
}
356
357
int PSPMsgDialog::Shutdown(bool force) {
358
if (GetStatus() != SCE_UTILITY_STATUS_FINISHED && !force)
359
return SCE_ERROR_UTILITY_INVALID_STATUS;
360
361
PSPDialog::Shutdown(force);
362
if (!force) {
363
ChangeStatusShutdown(MSG_SHUTDOWN_DELAY_US);
364
}
365
366
return 0;
367
}
368
369
void PSPMsgDialog::DoState(PointerWrap &p)
370
{
371
PSPDialog::DoState(p);
372
373
auto s = p.Section("PSPMsgDialog", 1);
374
if (!s)
375
return;
376
377
Do(p, flag);
378
Do(p, messageDialog);
379
Do(p, messageDialogAddr);
380
DoArray(p, msgText, sizeof(msgText));
381
Do(p, yesnoChoice);
382
383
// We don't save state this, you'll just have to scroll down again.
384
if (p.mode == p.MODE_READ) {
385
scrollPos_ = 0.0f;
386
framesUpHeld_ = 0;
387
framesDownHeld_ = 0;
388
}
389
}
390
391
pspUtilityDialogCommon *PSPMsgDialog::GetCommonParam()
392
{
393
return &messageDialog.common;
394
}
395
396