Core: Less restrictive on when using FB mode
[mupen64plus-pandora.git] / source / mupen64plus-core / src / api / vidext.c
CommitLineData
451ab91e 1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2 * Mupen64plus-core - api/vidext.c *
3 * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ *
4 * Copyright (C) 2009 Richard Goedeken *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the *
18 * Free Software Foundation, Inc., *
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
20 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
21
22/* This file contains the Core video extension functions which will be exported
23 * outside of the core library.
24 */
25
26#include <stdlib.h>
27#include <string.h>
28#include <SDL.h>
29
30#define M64P_CORE_PROTOTYPES 1
31#include "m64p_types.h"
32#include "m64p_vidext.h"
33#include "vidext.h"
34#include "callbacks.h"
35#include "../osd/osd.h"
36
37#if SDL_VERSION_ATLEAST(2,0,0)
38#include "vidext_sdl2_compat.h"
39#endif
40
41#ifdef PANDORA
42#define USE_EGL_SDL 1
43#ifdef USE_EGL_SDL
44#include "eglport.h"
45#else
46#include <EGL/egl.h>
47#include <SDL_syswm.h>
48SDL_SysWMinfo sysWmInfo;
49static EGLSurface eglSurface = NULL;
50static EGLContext eglContext = NULL;
51static EGLConfig eglConfig = NULL;
52static EGLDisplay eglDisplay = NULL;
53#endif
54static int useFB = 0;
55#endif
56
57/* local variables */
58static m64p_video_extension_functions l_ExternalVideoFuncTable = {10, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
59static int l_VideoExtensionActive = 0;
60static int l_VideoOutputActive = 0;
61static int l_Fullscreen = 0;
62static SDL_Surface *l_pScreen = NULL;
63
64/* global function for use by frontend.c */
65m64p_error OverrideVideoFunctions(m64p_video_extension_functions *VideoFunctionStruct)
66{
67 /* check input data */
68 if (VideoFunctionStruct == NULL)
69 return M64ERR_INPUT_ASSERT;
70 if (VideoFunctionStruct->Functions < 11)
71 return M64ERR_INPUT_INVALID;
72
73 /* disable video extension if any of the function pointers are NULL */
74 if (VideoFunctionStruct->VidExtFuncInit == NULL ||
75 VideoFunctionStruct->VidExtFuncQuit == NULL ||
76 VideoFunctionStruct->VidExtFuncListModes == NULL ||
77 VideoFunctionStruct->VidExtFuncSetMode == NULL ||
78 VideoFunctionStruct->VidExtFuncGLGetProc == NULL ||
79 VideoFunctionStruct->VidExtFuncGLSetAttr == NULL ||
80 VideoFunctionStruct->VidExtFuncGLGetAttr == NULL ||
81 VideoFunctionStruct->VidExtFuncGLSwapBuf == NULL ||
82 VideoFunctionStruct->VidExtFuncSetCaption == NULL ||
83 VideoFunctionStruct->VidExtFuncToggleFS == NULL ||
84 VideoFunctionStruct->VidExtFuncResizeWindow == NULL)
85 {
86 l_ExternalVideoFuncTable.Functions = 11;
87 memset(&l_ExternalVideoFuncTable.VidExtFuncInit, 0, 11 * sizeof(void *));
88 l_VideoExtensionActive = 0;
89 return M64ERR_SUCCESS;
90 }
91
92 /* otherwise copy in the override function pointers */
93 memcpy(&l_ExternalVideoFuncTable, VideoFunctionStruct, sizeof(m64p_video_extension_functions));
94 l_VideoExtensionActive = 1;
95 return M64ERR_SUCCESS;
96}
97
98int VidExt_InFullscreenMode(void)
99{
100 return l_Fullscreen;
101}
102
103int VidExt_VideoRunning(void)
104{
105 return l_VideoOutputActive;
106}
107
108/* video extension functions to be called by the video plugin */
109EXPORT m64p_error CALL VidExt_Init(void)
110{
111 /* call video extension override if necessary */
112 if (l_VideoExtensionActive)
113 return (*l_ExternalVideoFuncTable.VidExtFuncInit)();
114
115 if (SDL_InitSubSystem(SDL_INIT_VIDEO) == -1)
116 {
117 DebugMessage(M64MSG_ERROR, "SDL video subsystem init failed: %s", SDL_GetError());
118 return M64ERR_SYSTEM_FAIL;
119 }
120
121 return M64ERR_SUCCESS;
122}
123
124EXPORT m64p_error CALL VidExt_Quit(void)
125{
126 /* call video extension override if necessary */
127 if (l_VideoExtensionActive)
128 {
129 m64p_error rval = (*l_ExternalVideoFuncTable.VidExtFuncQuit)();
130 if (rval == M64ERR_SUCCESS)
131 {
132 l_VideoOutputActive = 0;
133 StateChanged(M64CORE_VIDEO_MODE, M64VIDEO_NONE);
134 }
135 return rval;
136 }
137
138 if (!SDL_WasInit(SDL_INIT_VIDEO))
139 return M64ERR_NOT_INIT;
140 #ifdef PANDORA
141 #ifdef USE_EGL_SDL
142 EGL_Close();
143 #else
144 if (eglDisplay) {
145 eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
146 if (eglContext)
147 eglDestroyContext(eglDisplay, eglContext);
148 if (eglSurface)
149 eglDestroySurface(eglDisplay, eglSurface);
150 eglTerminate(eglDisplay);
151 }
152 eglDisplay = NULL;
153 eglContext = NULL;
154 eglSurface = NULL;
155 #endif
156 #endif
157 SDL_ShowCursor(SDL_ENABLE);
158 SDL_QuitSubSystem(SDL_INIT_VIDEO);
159 l_pScreen = NULL;
160 l_VideoOutputActive = 0;
161 StateChanged(M64CORE_VIDEO_MODE, M64VIDEO_NONE);
162
163 return M64ERR_SUCCESS;
164}
165
166EXPORT m64p_error CALL VidExt_ListFullscreenModes(m64p_2d_size *SizeArray, int *NumSizes)
167{
168 const SDL_VideoInfo *videoInfo;
169 unsigned int videoFlags;
170 SDL_Rect **modes;
171 int i;
172
173 /* call video extension override if necessary */
174 if (l_VideoExtensionActive)
175 return (*l_ExternalVideoFuncTable.VidExtFuncListModes)(SizeArray, NumSizes);
176
177 if (!SDL_WasInit(SDL_INIT_VIDEO))
178 return M64ERR_NOT_INIT;
179
180 /* get a list of SDL video modes */
181 #ifdef PANDORA
182 videoFlags = SDL_FULLSCREEN;
183 #else
184 videoFlags = SDL_OPENGL | SDL_FULLSCREEN;
185 #endif
186
187 if ((videoInfo = SDL_GetVideoInfo()) == NULL)
188 {
189 DebugMessage(M64MSG_ERROR, "SDL_GetVideoInfo query failed: %s", SDL_GetError());
190 return M64ERR_SYSTEM_FAIL;
191 }
192 #ifndef PANDORA
193 if(videoInfo->hw_available)
194 videoFlags |= SDL_HWSURFACE;
195 else
196 #endif
197 videoFlags |= SDL_SWSURFACE;
198
199 modes = SDL_ListModes(NULL, videoFlags);
200
201 if (modes == (SDL_Rect **) 0 || modes == (SDL_Rect **) -1)
202 {
203 DebugMessage(M64MSG_WARNING, "No fullscreen SDL video modes available");
204 *NumSizes = 0;
205 return M64ERR_SUCCESS;
206 }
207
208 i = 0;
209 while (i < *NumSizes && modes[i] != NULL)
210 {
211 SizeArray[i].uiWidth = modes[i]->w;
212 SizeArray[i].uiHeight = modes[i]->h;
213 i++;
214 }
215
216 *NumSizes = i;
217
218 return M64ERR_SUCCESS;
219}
220
221EXPORT m64p_error CALL VidExt_SetVideoMode(int Width, int Height, int BitsPerPixel, m64p_video_mode ScreenMode, m64p_video_flags Flags)
222{
223 const SDL_VideoInfo *videoInfo;
224 int videoFlags = 0;
225printf("VidExt_SetVideoMode(%i, %i, %i, %s, %x)\n", Width, Height, BitsPerPixel, (ScreenMode==M64VIDEO_WINDOWED)?"Windowed":"Fullscreen", Flags);
226 /* call video extension override if necessary */
227 if (l_VideoExtensionActive)
228 {
229 m64p_error rval = (*l_ExternalVideoFuncTable.VidExtFuncSetMode)(Width, Height, BitsPerPixel, ScreenMode, Flags);
230 l_Fullscreen = (rval == M64ERR_SUCCESS && ScreenMode == M64VIDEO_FULLSCREEN);
231 l_VideoOutputActive = (rval == M64ERR_SUCCESS);
232 if (l_VideoOutputActive)
233 {
234 StateChanged(M64CORE_VIDEO_MODE, ScreenMode);
235 StateChanged(M64CORE_VIDEO_SIZE, (Width << 16) | Height);
236 }
237 return rval;
238 }
239
240 if (!SDL_WasInit(SDL_INIT_VIDEO)) {
241printf("SDL was not init, aborting\n");
242 return M64ERR_NOT_INIT;
243 }
244
245 /* Get SDL video flags to use */
246 if (ScreenMode == M64VIDEO_WINDOWED)
247 {
248 #ifdef PANDORA
249 videoFlags = 0;
250 useFB = 0;
251 #else
252 videoFlags = SDL_OPENGL;
253 #endif
254 if (Flags & M64VIDEOFLAG_SUPPORT_RESIZING)
255 videoFlags |= SDL_RESIZABLE;
256 }
257 else if (ScreenMode == M64VIDEO_FULLSCREEN)
258 {
259 #ifdef PANDORA
260 videoFlags = SDL_FULLSCREEN;
261 #else
262 videoFlags = SDL_OPENGL | SDL_FULLSCREEN;
263 #endif
264 }
265 else
266 {
267 return M64ERR_INPUT_INVALID;
268 }
269
270 if ((videoInfo = SDL_GetVideoInfo()) == NULL)
271 {
272 DebugMessage(M64MSG_ERROR, "SDL_GetVideoInfo query failed: %s", SDL_GetError());
273 return M64ERR_SYSTEM_FAIL;
274 }
275 #ifndef PANDORA
276 if (videoInfo->hw_available)
277 videoFlags |= SDL_HWSURFACE;
278 else
279 #endif
280 videoFlags |= SDL_SWSURFACE;
281
282 /* set the mode */
283 if (BitsPerPixel > 0)
284 DebugMessage(M64MSG_INFO, "Setting %i-bit video mode: %ix%i", BitsPerPixel, Width, Height);
285 else
286 DebugMessage(M64MSG_INFO, "Setting video mode: %ix%i", Width, Height);
287
288 l_pScreen = SDL_SetVideoMode(Width, Height, BitsPerPixel, videoFlags);
289 if (l_pScreen == NULL)
290 {
291 DebugMessage(M64MSG_ERROR, "SDL_SetVideoMode failed: %s", SDL_GetError());
292 return M64ERR_SYSTEM_FAIL;
293 }
294 #ifdef PANDORA
295 // Setup EGL Context...
9e0ef69c 296// if ((Width==800) && (Height==480)) // 800x480 => FB and no X11
451ab91e 297 useFB = 1;
298// Width = 800;
299// Height = 480;
300 #ifdef USE_EGL_SDL
301 EGL_Open(Width, Height);
302 #else
303 EGLint maj, min;
304 SDL_VERSION(&sysWmInfo.version);
305 SDL_GetWMInfo(&sysWmInfo);
306 eglDisplay = eglGetDisplay( (useFB)? EGL_DEFAULT_DISPLAY:(EGLNativeDisplayType)sysWmInfo.info.x11.display );
307 if (!eglInitialize(eglDisplay, &maj, &min)) {
308 printf("eglinfo: eglInitialize failed\n");
309 }
310 printf("EGL v%i.%i initialized%s\n", maj, min, (useFB)?" using FB":"");
311 EGLint attribs[] =
312 {
313 EGL_RED_SIZE, 5,
314 EGL_GREEN_SIZE, 6,
315 EGL_BLUE_SIZE, 5,
316 EGL_DEPTH_SIZE, 16,
317 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
318 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
319 EGL_SAMPLE_BUFFERS, 0,
320 EGL_SAMPLES, 0,
321 EGL_NONE
322 };
323 EGLint ctx_attribs[] =
324 {
325 EGL_CONTEXT_CLIENT_VERSION, 2,
326 EGL_NONE
327 };
328
329
330 EGLint num_configs;
331 if (!eglChooseConfig(eglDisplay, attribs, &eglConfig, 1, &num_configs) || (num_configs < 1))
332 {
333 printf("Could not find config for %s (perhaps this API is unsupported?)\n", "GLES2");
334 }
335
336 EGLint vid;
337 if (!eglGetConfigAttrib(eglDisplay, eglConfig, EGL_NATIVE_VISUAL_ID, &vid))
338 {
339 printf("Could not get native visual ID from chosen config\n");
340 }
341 eglBindAPI(EGL_OPENGL_ES_API);
342
343 eglContext=eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, ctx_attribs);
344 eglSurface=eglCreateWindowSurface(eglDisplay, eglConfig, (NativeWindowType)((useFB)?NULL:sysWmInfo.info.x11.window), NULL);
345
346 if (!eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext))
347 {
348 printf("eglMakeCurrent() failed\n");
349 }
350 #endif //eglport
351 #endif
352
353 SDL_ShowCursor(SDL_DISABLE);
354
355 l_Fullscreen = (ScreenMode == M64VIDEO_FULLSCREEN);
356 l_VideoOutputActive = 1;
357 StateChanged(M64CORE_VIDEO_MODE, ScreenMode);
358 StateChanged(M64CORE_VIDEO_SIZE, (Width << 16) | Height);
359 return M64ERR_SUCCESS;
360}
361
362EXPORT m64p_error CALL VidExt_ResizeWindow(int Width, int Height)
363{
364 const SDL_VideoInfo *videoInfo;
365 int videoFlags = 0;
366
367 /* call video extension override if necessary */
368 if (l_VideoExtensionActive)
369 {
370 m64p_error rval;
371 // shut down the OSD
372 osd_exit();
373 // re-create the OGL context
374 rval = (*l_ExternalVideoFuncTable.VidExtFuncResizeWindow)(Width, Height);
375 if (rval == M64ERR_SUCCESS)
376 {
377 StateChanged(M64CORE_VIDEO_SIZE, (Width << 16) | Height);
378 // re-create the On-Screen Display
379 osd_init(Width, Height);
380 }
381 return rval;
382 }
383
384 if (!l_VideoOutputActive || !SDL_WasInit(SDL_INIT_VIDEO))
385 return M64ERR_NOT_INIT;
386
387 if (l_Fullscreen)
388 {
389 DebugMessage(M64MSG_ERROR, "VidExt_ResizeWindow() called in fullscreen mode.");
390 return M64ERR_INVALID_STATE;
391 }
392
393 /* Get SDL video flags to use */
394 #ifdef PANDORA
395 if (useFB)
396 return M64ERR_INVALID_STATE;
397 videoFlags = SDL_RESIZABLE;
398 #else
399 videoFlags = SDL_OPENGL | SDL_RESIZABLE;
400 #endif
401 if ((videoInfo = SDL_GetVideoInfo()) == NULL)
402 {
403 DebugMessage(M64MSG_ERROR, "SDL_GetVideoInfo query failed: %s", SDL_GetError());
404 return M64ERR_SYSTEM_FAIL;
405 }
406 #ifndef PANDORA
407 if (videoInfo->hw_available)
408 videoFlags |= SDL_HWSURFACE;
409 else
410 #endif
411 videoFlags |= SDL_SWSURFACE;
412
413 // destroy the On-Screen Display
414 osd_exit();
415
416 /* set the re-sizing the screen will create a new OpenGL context */
417 l_pScreen = SDL_SetVideoMode(Width, Height, 0, videoFlags);
418 if (l_pScreen == NULL)
419 {
420 DebugMessage(M64MSG_ERROR, "SDL_SetVideoMode failed: %s", SDL_GetError());
421 return M64ERR_SYSTEM_FAIL;
422 }
423
424 StateChanged(M64CORE_VIDEO_SIZE, (Width << 16) | Height);
425 // re-create the On-Screen Display
426 osd_init(Width, Height);
427 return M64ERR_SUCCESS;
428}
429
430EXPORT m64p_error CALL VidExt_SetCaption(const char *Title)
431{
432 /* call video extension override if necessary */
433 if (l_VideoExtensionActive)
434 return (*l_ExternalVideoFuncTable.VidExtFuncSetCaption)(Title);
435
436 if (!SDL_WasInit(SDL_INIT_VIDEO))
437 return M64ERR_NOT_INIT;
438
439 SDL_WM_SetCaption(Title, "M64+ Video");
440
441 return M64ERR_SUCCESS;
442}
443
444EXPORT m64p_error CALL VidExt_ToggleFullScreen(void)
445{
446 /* call video extension override if necessary */
447 if (l_VideoExtensionActive)
448 {
449 m64p_error rval = (*l_ExternalVideoFuncTable.VidExtFuncToggleFS)();
450 if (rval == M64ERR_SUCCESS)
451 {
452 l_Fullscreen = !l_Fullscreen;
453 StateChanged(M64CORE_VIDEO_MODE, l_Fullscreen ? M64VIDEO_FULLSCREEN : M64VIDEO_WINDOWED);
454 }
455 return rval;
456 }
457
458 if (!SDL_WasInit(SDL_INIT_VIDEO))
459 return M64ERR_NOT_INIT;
460
461 /* TODO:
462 * SDL_WM_ToggleFullScreen doesn't work under Windows and others
463 * (see http://wiki.libsdl.org/moin.cgi/FAQWindows for explanation).
464 * Instead, we should call SDL_SetVideoMode with the SDL_FULLSCREEN flag.
465 * (see http://sdl.beuc.net/sdl.wiki/SDL_SetVideoMode), but on Windows
466 * this resets the OpenGL context and video plugins don't support it yet.
467 * Uncomment the next line to test it: */
468 //return VidExt_SetVideoMode(l_pScreen->w, l_pScreen->h, l_pScreen->format->BitsPerPixel, l_Fullscreen ? M64VIDEO_WINDOWED : M64VIDEO_FULLSCREEN);
469/* #ifdef PANDORA
470 if (useFB) // no effect on FB
471 return M64ERR_INVALID_STATE;
472 #endif*/
473 if (SDL_WM_ToggleFullScreen(l_pScreen) == 1)
474 {
475 l_Fullscreen = !l_Fullscreen;
476 StateChanged(M64CORE_VIDEO_MODE, l_Fullscreen ? M64VIDEO_FULLSCREEN : M64VIDEO_WINDOWED);
477 return M64ERR_SUCCESS;
478 }
479
480 return M64ERR_SYSTEM_FAIL;
481}
482
483EXPORT void * CALL VidExt_GL_GetProcAddress(const char* Proc)
484{
485 /* call video extension override if necessary */
486 if (l_VideoExtensionActive)
487 return (*l_ExternalVideoFuncTable.VidExtFuncGLGetProc)(Proc);
488
489 if (!SDL_WasInit(SDL_INIT_VIDEO))
490 return NULL;
491
492 return SDL_GL_GetProcAddress(Proc);
493}
494
495typedef struct {
496 m64p_GLattr m64Attr;
497 SDL_GLattr sdlAttr;
498} GLAttrMapNode;
499
500static const GLAttrMapNode GLAttrMap[] = {
501 { M64P_GL_DOUBLEBUFFER, SDL_GL_DOUBLEBUFFER },
502 { M64P_GL_BUFFER_SIZE, SDL_GL_BUFFER_SIZE },
503 { M64P_GL_DEPTH_SIZE, SDL_GL_DEPTH_SIZE },
504 { M64P_GL_RED_SIZE, SDL_GL_RED_SIZE },
505 { M64P_GL_GREEN_SIZE, SDL_GL_GREEN_SIZE },
506 { M64P_GL_BLUE_SIZE, SDL_GL_BLUE_SIZE },
507 { M64P_GL_ALPHA_SIZE, SDL_GL_ALPHA_SIZE },
508#if SDL_VERSION_ATLEAST(1,3,0)
509 { M64P_GL_SWAP_CONTROL, SDL_RENDERER_PRESENTVSYNC },
510#else
511 { M64P_GL_SWAP_CONTROL, SDL_GL_SWAP_CONTROL },
512#endif
513 { M64P_GL_MULTISAMPLEBUFFERS, SDL_GL_MULTISAMPLEBUFFERS },
514 { M64P_GL_MULTISAMPLESAMPLES, SDL_GL_MULTISAMPLESAMPLES }};
515static const int mapSize = sizeof(GLAttrMap) / sizeof(GLAttrMapNode);
516
517EXPORT m64p_error CALL VidExt_GL_SetAttribute(m64p_GLattr Attr, int Value)
518{
519 int i;
520
521 /* call video extension override if necessary */
522 if (l_VideoExtensionActive)
523 return (*l_ExternalVideoFuncTable.VidExtFuncGLSetAttr)(Attr, Value);
524
525 if (!SDL_WasInit(SDL_INIT_VIDEO))
526 return M64ERR_NOT_INIT;
527
528 #ifdef PANDORA
529 return M64ERR_SUCCESS;
530 #else
531 for (i = 0; i < mapSize; i++)
532 {
533 if (GLAttrMap[i].m64Attr == Attr)
534 {
535 if (SDL_GL_SetAttribute(GLAttrMap[i].sdlAttr, Value) != 0)
536 return M64ERR_SYSTEM_FAIL;
537 return M64ERR_SUCCESS;
538 }
539 }
540
541 return M64ERR_INPUT_INVALID;
542 #endif
543}
544
545EXPORT m64p_error CALL VidExt_GL_GetAttribute(m64p_GLattr Attr, int *pValue)
546{
547 int i;
548
549 /* call video extension override if necessary */
550 if (l_VideoExtensionActive)
551 return (*l_ExternalVideoFuncTable.VidExtFuncGLGetAttr)(Attr, pValue);
552
553 if (!SDL_WasInit(SDL_INIT_VIDEO))
554 return M64ERR_NOT_INIT;
555
556 #ifdef PANDORA
557 return M64ERR_SUCCESS;
558 #else
559 for (i = 0; i < mapSize; i++)
560 {
561 if (GLAttrMap[i].m64Attr == Attr)
562 {
563 int NewValue = 0;
564 if (SDL_GL_GetAttribute(GLAttrMap[i].sdlAttr, &NewValue) != 0)
565 return M64ERR_SYSTEM_FAIL;
566 *pValue = NewValue;
567 return M64ERR_SUCCESS;
568 }
569 }
570
571 return M64ERR_INPUT_INVALID;
572 #endif
573}
574
575EXPORT m64p_error CALL VidExt_GL_SwapBuffers(void)
576{
577 /* call video extension override if necessary */
578 if (l_VideoExtensionActive)
579 return (*l_ExternalVideoFuncTable.VidExtFuncGLSwapBuf)();
580
581 if (!SDL_WasInit(SDL_INIT_VIDEO))
582 return M64ERR_NOT_INIT;
583 #ifdef PANDORA
584 #ifdef USE_EGL_SDL
585 EGL_SwapBuffers();
586 #else
587 eglSwapBuffers( eglDisplay, eglSurface );
588 #endif
589 #else
590 SDL_GL_SwapBuffers();
591 #endif
592 return M64ERR_SUCCESS;
593}
594
595