SceError: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
|||
(23 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
== Module == | == Module == | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
! Version | ! Version !! World !! Privilege | ||
|- | |- | ||
| 1.69 | | 1.69-3.60 || Non-secure || Kernel | ||
|} | |} | ||
Line 19: | Line 18: | ||
| 1.69 || [[SceError#SceErrorForDriver|SceErrorForDriver]] || Non-secure || Kernel || 0xBA576248 | | 1.69 || [[SceError#SceErrorForDriver|SceErrorForDriver]] || Non-secure || Kernel || 0xBA576248 | ||
|- | |- | ||
| 1.69 || [[SceError#SceError|SceError]] || Non-secure || User || 0x5CD2CAD1 | | 1.69-3.60 || [[SceError#SceError|SceError]] || Non-secure || User || 0x5CD2CAD1 | ||
|} | |} | ||
== SceErrorForDriver | = Types = | ||
<source lang="C"> | |||
typedef struct SceErrorDefaultFormat { | |||
int data1; | |||
int data2; | |||
} SceErrorDefaultFormat; | |||
typedef struct SceErrorStrings { | |||
char s[0x10]; | |||
} SceErrorStrings; | |||
typedef struct SceErrorHistoryPostInfo { // size is 0x180 | |||
char error_message[0x100]; | |||
char data_0x100[0x40]; // unknown | |||
int error_code_hex; | |||
int data_0x174; | |||
SceUInt version; // from SceKernelFwInfo | |||
int data_0x17C; | |||
char titleid[0xC]; | |||
int data_0x15C; // from SceKernelFwInfo->unk_24 | |||
char data_0x160[0x20]; | |||
} SceErrorHistoryPostInfo; | |||
typedef struct SceErrorHistoryInfo { | |||
int data_0x00; | |||
int data_0x04; | |||
SceInt64 time; | |||
SceErrorStrings error_code; | |||
char data_0x20[0x10]; | |||
SceErrorHistoryPostInfo post; | |||
} SceErrorHistoryInfo; | |||
typedef struct SceErrorSequenceInfo { // size is 0x20 | |||
int data[8]; | |||
} SceErrorSequenceInfo; | |||
</source> | |||
= SceErrorForDriver = | |||
= SceError = | |||
=== _sceErrorHistoryUpdateSequenceInfo === | === _sceErrorHistoryUpdateSequenceInfo === | ||
Line 31: | Line 67: | ||
! Version !! NID | ! Version !! NID | ||
|- | |- | ||
| 1.69 | | 1.69-3.60 || 0x6FBE4BDC | ||
|} | |} | ||
<source lang="c">int _sceErrorHistoryUpdateSequenceInfo(const SceErrorSequenceInfo *info, int a2);</source> | |||
=== _sceErrorHistoryPostError === | === _sceErrorHistoryPostError === | ||
{| class="wikitable" | {| class="wikitable" | ||
Line 40: | Line 77: | ||
! Version !! NID | ! Version !! NID | ||
|- | |- | ||
| 1.69 | | 1.69-3.60 || 0x70F9D872 | ||
|} | |} | ||
<source lang="c"> | <source lang="c">int _sceErrorHistoryPostError(const SceErrorHistoryPostInfo *info);</source> | ||
int _sceErrorHistoryPostError( | |||
</source> | |||
=== _sceErrorGetExternalString === | === _sceErrorGetExternalString === | ||
Line 55: | Line 87: | ||
! Version !! NID | ! Version !! NID | ||
|- | |- | ||
| 1.69 | | 1.69-3.60 || 0x85747003 | ||
|} | |} | ||
<source lang="C"> | <source lang="C">int _sceErrorGetExternalString(SceErrorStrings *error_strings, int error_code);</source> | ||
int _sceErrorGetExternalString( | |||
</source> | |||
=== _sceErrorHistorySetDefaultFormat === | === _sceErrorHistorySetDefaultFormat === | ||
Line 70: | Line 97: | ||
! Version !! NID | ! Version !! NID | ||
|- | |- | ||
| 1.69 | | 1.69-3.60 || 0xB94DAA2F | ||
|} | |} | ||
<source lang="c"> | <source lang="c">int _sceErrorHistorySetDefaultFormat(const SceErrorDefaultFormat *a1);</source> | ||
int _sceErrorHistorySetDefaultFormat( | |||
</source> | |||
=== _sceErrorHistoryClearError === | === _sceErrorHistoryClearError === | ||
Line 85: | Line 107: | ||
! Version !! NID | ! Version !! NID | ||
|- | |- | ||
| 1.69 | | 1.69-3.60 || 0xC88F479E | ||
|} | |} | ||
<source lang="c"> | <source lang="c"> | ||
//argument can be only 0, or SCE_ERROR_ERRNO_EINVAL error will be returned | // argument can be only 0, or SCE_ERROR_ERRNO_EINVAL error will be returned | ||
int _sceErrorHistoryClearError(int | int _sceErrorHistoryClearError(int ch); | ||
</source> | </source> | ||
Line 100: | Line 120: | ||
! Version !! NID | ! Version !! NID | ||
|- | |- | ||
| 1.69 | | 1.69-3.60 || 0xF16DF981 | ||
|} | |} | ||
<source lang="c"> | <source lang="c">int _sceErrorHistoryGetError(SceUInt32 error_idx, SceErrorHistoryInfo *info);</source> | ||
int _sceErrorHistoryGetError ( | = Error table translation = | ||
</ | |||
In various places PSVita may display errors translated into their string representation. The code below also does this, provided you feed it with a copy of <code>os0:kd/error_table.bin</code>. | |||
<source lang="c"> | <source lang="c"> | ||
#include <stdlib.h> | #include <stdlib.h> | ||
Line 120: | Line 137: | ||
#endif | #endif | ||
int read_error_table(const char *path, void *buf) | int read_error_table(const char *path, void *buf) { | ||
{ | |||
unsigned int v6; // r1@6 | unsigned int v6; // r1@6 | ||
unsigned int v7; // r1@10 | unsigned int v7; // r1@10 | ||
FILE *f = fopen(path, "rb"); | FILE *f = fopen(path, "rb"); | ||
if ( f ) | if ( f ) { | ||
if ( fread(buf, 1, 8, f) == 8 ) { | |||
if ( fread(buf, 1, 8, f) == 8 ) | |||
v6 = *((short *)buf + 2); | v6 = *((short *)buf + 2); | ||
if ( v6 <= 0x20 ) | if ( v6 <= 0x20 ) | ||
{ | { | ||
if ( fread(*((int *)buf + 2), 1, 8 * v6, f) == 8 * v6 | if ( fread(*((int *)buf + 2), 1, 8 * v6, f) == 8 * v6 | ||
&& fread((char *)buf + 12, 1, 4, f) == 4 ) | && fread((char *)buf + 12, 1, 4, f) == 4 ) { | ||
v7 = *((short *)buf + 7); | v7 = *((short *)buf + 7); | ||
if ( v7 <= 0x36B0 ) | if ( v7 <= 0x36B0 ) { | ||
if ( fread(*((int *)buf + 4), 1, 4 * v7, f) == 4 * v7 ) { | |||
if ( fread(*((int *)buf + 4), 1, 4 * v7, f) == 4 * v7 ) | |||
fclose(f); | fclose(f); | ||
return 0; | return 0; | ||
Line 146: | Line 157: | ||
} | } | ||
else | else | ||
fprintf(stderr, "[SceError] error_table_num = %d\n", v7); | fprintf(stderr, "[SceError] error_table_num = %d\n", v7); | ||
} | } | ||
} | } | ||
else | else | ||
fprintf(stderr, "[SceError] facility_group_num = %d\n", v6); | fprintf(stderr, "[SceError] facility_group_num = %d\n", v6); | ||
} | } | ||
fclose(f); | fclose(f); | ||
Line 171: | Line 178: | ||
#define _BYTE char | #define _BYTE char | ||
int error_code_to_string(int error_table, char *output, unsigned int error_code) | int error_code_to_string(int error_table, char *output, unsigned int error_code) { | ||
{ | |||
signed int v4; // r7@2 | signed int v4; // r7@2 | ||
int v5; // r4@2 | int v5; // r4@2 | ||
Line 214: | Line 220: | ||
if (!*(_WORD *)(error_table + 14)) | if (!*(_WORD *)(error_table + 14)) | ||
goto LABEL_46; | goto LABEL_46; | ||
if (*(_DWORD *)v5 == error_code) | if (*(_DWORD *)v5 == error_code) { | ||
v6 = 0; | v6 = 0; | ||
goto LABEL_17; | goto LABEL_17; | ||
Line 224: | Line 229: | ||
goto LABEL_11; | goto LABEL_11; | ||
v6 = 1; | v6 = 1; | ||
if (v4 <= 1) | if (v4 <= 1) { | ||
LABEL_46: | LABEL_46: | ||
snprintf(output, 16, "E-%08x", error_code); | snprintf(output, 16, "E-%08x", error_code); | ||
Line 232: | Line 236: | ||
v8 = *(_DWORD *)(v5 + 4); | v8 = *(_DWORD *)(v5 + 4); | ||
v5 += 4; | v5 += 4; | ||
if (v8 != error_code) | if (v8 != error_code) { | ||
if (v7 == 1 | if (v7 == 1 | ||
|| (v7 == 2 || (v9 = *(_DWORD *)(v5 + 4), v5 += 4, v6 = 2, v9 != error_code)) | || (v7 == 2 || (v9 = *(_DWORD *)(v5 + 4), v5 += 4, v6 = 2, v9 != error_code)) | ||
&& (v10 = *(_DWORD *)(v5 + 4), v5 += 4, ++v6, v10 != error_code)) | && (v10 = *(_DWORD *)(v5 + 4), v5 += 4, ++v6, v10 != error_code)) { | ||
LABEL_11: | LABEL_11: | ||
while (1) | while (1) { | ||
++v6; | ++v6; | ||
v11 = v5; | v11 = v5; | ||
Line 250: | Line 251: | ||
v15 = *(_DWORD *)(v11 + 4); | v15 = *(_DWORD *)(v11 + 4); | ||
v14 = v11 + 4; | v14 = v11 + 4; | ||
if (v15 != error_code) | if (v15 != error_code) { | ||
v17 = *(_DWORD *)(v14 + 4); | v17 = *(_DWORD *)(v14 + 4); | ||
v16 = v14 + 4; | v16 = v14 + 4; | ||
++v6; | ++v6; | ||
if (v17 != error_code) | if (v17 != error_code) { | ||
v6 = v13 + 2; | v6 = v13 + 2; | ||
if (*(_DWORD *)(v16 + 4) != error_code) | if (*(_DWORD *)(v16 + 4) != error_code) { | ||
v6 = v13 + 3; | v6 = v13 + 3; | ||
if (*(_DWORD *)(v12 + 4) != error_code) | if (*(_DWORD *)(v12 + 4) != error_code) | ||
Line 270: | Line 268: | ||
} | } | ||
} | } | ||
if (*(_WORD *)(error_table + 6) < v6) | if (*(_WORD *)(error_table + 6) < v6) { | ||
snprintf(output, 16, "*-%08x", error_code); | snprintf(output, 16, "*-%08x", error_code); | ||
return 0; | return 0; | ||
Line 279: | Line 276: | ||
v19 = (error_code >> 16) & 0xFFF; | v19 = (error_code >> 16) & 0xFFF; | ||
v20 = *(_DWORD *)(error_table + 8); | v20 = *(_DWORD *)(error_table + 8); | ||
if (*(_WORD *)(error_table + 4)) | if (*(_WORD *)(error_table + 4)) { | ||
v21 = 0; | v21 = 0; | ||
v22 = ((_BYTE)v18 - 1) & 3; | v22 = ((_BYTE)v18 - 1) & 3; | ||
if (!(((_BYTE)v18 - 1) & 3)) | if (!(((_BYTE)v18 - 1) & 3)) { | ||
LABEL_35: | LABEL_35: | ||
while (1) | while (1) { | ||
v30 = *(_WORD *)v20; | v30 = *(_WORD *)v20; | ||
if ((signed int)v19 >= v30 && (signed int)v19 < *(_WORD *)(v20 + 2) + v30) | if ((signed int)v19 >= v30 && (signed int)v19 < *(_WORD *)(v20 + 2) + v30) | ||
Line 312: | Line 306: | ||
} | } | ||
v23 = *(_WORD *)v20; | v23 = *(_WORD *)v20; | ||
if ((signed int)v19 >= v23 && (signed int)v19 < *(_WORD *)(v20 + 2) + v23) | if ((signed int)v19 >= v23 && (signed int)v19 < *(_WORD *)(v20 + 2) + v23) { | ||
LABEL_48: | LABEL_48: | ||
v32 = v20 + 4; | v32 = v20 + 4; | ||
Line 319: | Line 312: | ||
} | } | ||
v21 = 1; | v21 = 1; | ||
if (v18 > 1) | if (v18 > 1) { | ||
v20 += 8; | v20 += 8; | ||
if (v22 == 1) | if (v22 == 1) | ||
goto LABEL_35; | goto LABEL_35; | ||
if (v22 != 2) | if (v22 != 2) { | ||
v24 = *(_WORD *)v20; | v24 = *(_WORD *)v20; | ||
if ((signed int)v19 >= v24 && (signed int)v19 < *(_WORD *)(v20 + 2) + v24) | if ((signed int)v19 >= v24 && (signed int)v19 < *(_WORD *)(v20 + 2) + v24) { | ||
v32 = v20 + 4; | v32 = v20 + 4; | ||
goto LABEL_39; | goto LABEL_39; | ||
Line 336: | Line 326: | ||
} | } | ||
v25 = *(_WORD *)v20; | v25 = *(_WORD *)v20; | ||
if ((signed int)v19 < v25 || (signed int)v19 >= *(_WORD *)(v20 + 2) + v25) | if ((signed int)v19 < v25 || (signed int)v19 >= *(_WORD *)(v20 + 2) + v25) { | ||
++v21; | ++v21; | ||
v20 += 8; | v20 += 8; | ||
Line 349: | Line 338: | ||
LABEL_39: | LABEL_39: | ||
v33 = v6 + *(_WORD *)(error_table + 12); | v33 = v6 + *(_WORD *)(error_table + 12); | ||
if (v33) | if (v33) { | ||
v35 = v6 + *(_WORD *)(error_table + 12); | v35 = v6 + *(_WORD *)(error_table + 12); | ||
v36 = 0; | v36 = 0; | ||
do | do { | ||
v37 = v35 % 10; | v37 = v35 % 10; | ||
v35 /= 10; | v35 /= 10; | ||
Line 362: | Line 349: | ||
} | } | ||
else | else | ||
v34 = 0; | v34 = 0; | ||
snprintf(output, 16, "%s-%d-%1d", v32, v33, v34); | snprintf(output, 16, "%s-%d-%1d", v32, v33, v34); | ||
return 0; | return 0; | ||
Line 392: | Line 377: | ||
</source> | </source> | ||
[[Category:ARM]] | |||
[[Category:Kernel]] | |||
[[Category:Modules]] | [[Category:Modules]] | ||
[[Category: | [[Category:Library]] |
Latest revision as of 21:01, 1 May 2023
Module
Version | World | Privilege |
---|---|---|
1.69-3.60 | Non-secure | Kernel |
Libraries
Known NIDs
Version | Name | World | Visibility | NID |
---|---|---|---|---|
1.69 | SceErrorForDriver | Non-secure | Kernel | 0xBA576248 |
1.69-3.60 | SceError | Non-secure | User | 0x5CD2CAD1 |
Types
typedef struct SceErrorDefaultFormat { int data1; int data2; } SceErrorDefaultFormat; typedef struct SceErrorStrings { char s[0x10]; } SceErrorStrings; typedef struct SceErrorHistoryPostInfo { // size is 0x180 char error_message[0x100]; char data_0x100[0x40]; // unknown int error_code_hex; int data_0x174; SceUInt version; // from SceKernelFwInfo int data_0x17C; char titleid[0xC]; int data_0x15C; // from SceKernelFwInfo->unk_24 char data_0x160[0x20]; } SceErrorHistoryPostInfo; typedef struct SceErrorHistoryInfo { int data_0x00; int data_0x04; SceInt64 time; SceErrorStrings error_code; char data_0x20[0x10]; SceErrorHistoryPostInfo post; } SceErrorHistoryInfo; typedef struct SceErrorSequenceInfo { // size is 0x20 int data[8]; } SceErrorSequenceInfo;
SceErrorForDriver
SceError
_sceErrorHistoryUpdateSequenceInfo
Version | NID |
---|---|
1.69-3.60 | 0x6FBE4BDC |
int _sceErrorHistoryUpdateSequenceInfo(const SceErrorSequenceInfo *info, int a2);
_sceErrorHistoryPostError
Version | NID |
---|---|
1.69-3.60 | 0x70F9D872 |
int _sceErrorHistoryPostError(const SceErrorHistoryPostInfo *info);
_sceErrorGetExternalString
Version | NID |
---|---|
1.69-3.60 | 0x85747003 |
int _sceErrorGetExternalString(SceErrorStrings *error_strings, int error_code);
_sceErrorHistorySetDefaultFormat
Version | NID |
---|---|
1.69-3.60 | 0xB94DAA2F |
int _sceErrorHistorySetDefaultFormat(const SceErrorDefaultFormat *a1);
_sceErrorHistoryClearError
Version | NID |
---|---|
1.69-3.60 | 0xC88F479E |
// argument can be only 0, or SCE_ERROR_ERRNO_EINVAL error will be returned int _sceErrorHistoryClearError(int ch);
_sceErrorHistoryGetError
Version | NID |
---|---|
1.69-3.60 | 0xF16DF981 |
int _sceErrorHistoryGetError(SceUInt32 error_idx, SceErrorHistoryInfo *info);
Error table translation
In various places PSVita may display errors translated into their string representation. The code below also does this, provided you feed it with a copy of os0:kd/error_table.bin
.
#include <stdlib.h> #include <stdio.h> #ifdef _MSC_VER #define snprintf _snprintf #endif int read_error_table(const char *path, void *buf) { unsigned int v6; // r1@6 unsigned int v7; // r1@10 FILE *f = fopen(path, "rb"); if ( f ) { if ( fread(buf, 1, 8, f) == 8 ) { v6 = *((short *)buf + 2); if ( v6 <= 0x20 ) { if ( fread(*((int *)buf + 2), 1, 8 * v6, f) == 8 * v6 && fread((char *)buf + 12, 1, 4, f) == 4 ) { v7 = *((short *)buf + 7); if ( v7 <= 0x36B0 ) { if ( fread(*((int *)buf + 4), 1, 4 * v7, f) == 4 * v7 ) { fclose(f); return 0; } } else fprintf(stderr, "[SceError] error_table_num = %d\n", v7); } } else fprintf(stderr, "[SceError] facility_group_num = %d\n", v6); } fclose(f); } *(int *)buf = 0; *((int *)buf + 1) = 0; *((int *)buf + 2) = 0; *((int *)buf + 3) = 0; *((int *)buf + 4) = 0; *((int *)buf + 5) = 0; return 0; } #define _WORD short #define _DWORD int #define _BYTE char int error_code_to_string(int error_table, char *output, unsigned int error_code) { signed int v4; // r7@2 int v5; // r4@2 int v6; // r2@4 int v7; // r5@4 int v8; // t1@6 int v9; // t1@9 int v10; // t1@10 int v11; // r5@11 int v12; // r12@11 int v13; // r6@11 int v14; // r5@12 int v15; // t1@12 int v16; // r5@13 int v17; // t1@13 signed int v18; // lr@17 unsigned int v19; // r3@17 int v20; // r4@17 int v21; // r6@18 int v22; // r5@18 signed int v23; // r6@19 signed int v24; // r5@23 signed int v25; // r5@25 signed int v26; // r7@28 int v27; // r5@28 signed int v28; // r7@30 signed int v29; // r7@32 signed int v30; // r5@35 int v31; // r6@37 int v32; // r3@38 int v33; // r4@39 int v34; // r5@40 signed int v35; // r2@41 signed int v36; // r6@41 int v37; // r7@42 if (!output) return 0; v4 = *(_WORD *)(error_table + 14); v5 = *(_DWORD *)(error_table + 16); if (!*(_WORD *)(error_table + 14)) goto LABEL_46; if (*(_DWORD *)v5 == error_code) { v6 = 0; goto LABEL_17; } v6 = 0; v7 = ((_BYTE)v4 - 1) & 3; if (!(((_BYTE)v4 - 1) & 3)) goto LABEL_11; v6 = 1; if (v4 <= 1) { LABEL_46: snprintf(output, 16, "E-%08x", error_code); return 0; } v8 = *(_DWORD *)(v5 + 4); v5 += 4; if (v8 != error_code) { if (v7 == 1 || (v7 == 2 || (v9 = *(_DWORD *)(v5 + 4), v5 += 4, v6 = 2, v9 != error_code)) && (v10 = *(_DWORD *)(v5 + 4), v5 += 4, ++v6, v10 != error_code)) { LABEL_11: while (1) { ++v6; v11 = v5; v12 = v5 + 12; v13 = v6; v5 += 16; if (v6 >= v4) goto LABEL_46; v15 = *(_DWORD *)(v11 + 4); v14 = v11 + 4; if (v15 != error_code) { v17 = *(_DWORD *)(v14 + 4); v16 = v14 + 4; ++v6; if (v17 != error_code) { v6 = v13 + 2; if (*(_DWORD *)(v16 + 4) != error_code) { v6 = v13 + 3; if (*(_DWORD *)(v12 + 4) != error_code) continue; } } } break; } } } if (*(_WORD *)(error_table + 6) < v6) { snprintf(output, 16, "*-%08x", error_code); return 0; } LABEL_17: v18 = *(_WORD *)(error_table + 4); v19 = (error_code >> 16) & 0xFFF; v20 = *(_DWORD *)(error_table + 8); if (*(_WORD *)(error_table + 4)) { v21 = 0; v22 = ((_BYTE)v18 - 1) & 3; if (!(((_BYTE)v18 - 1) & 3)) { LABEL_35: while (1) { v30 = *(_WORD *)v20; if ((signed int)v19 >= v30 && (signed int)v19 < *(_WORD *)(v20 + 2) + v30) goto LABEL_48; v31 = v21 + 1; if (v31 >= v18) goto LABEL_38; v20 += 8; v26 = *(_WORD *)v20; v27 = v20; if ((signed int)v19 >= v26 && (signed int)v19 < v26 + *(_WORD *)(v20 + 2)) goto LABEL_48; v28 = *(_WORD *)(v20 + 8); v20 += 8; if ((signed int)v19 >= v28 && (signed int)v19 < v28 + *(_WORD *)(v27 + 10)) goto LABEL_48; v29 = *(_WORD *)(v27 + 16); v20 = v27 + 16; if ((signed int)v19 >= v29 && (signed int)v19 < v29 + *(_WORD *)(v27 + 18)) goto LABEL_48; v21 = v31 + 3; v20 = v27 + 24; } } v23 = *(_WORD *)v20; if ((signed int)v19 >= v23 && (signed int)v19 < *(_WORD *)(v20 + 2) + v23) { LABEL_48: v32 = v20 + 4; goto LABEL_39; } v21 = 1; if (v18 > 1) { v20 += 8; if (v22 == 1) goto LABEL_35; if (v22 != 2) { v24 = *(_WORD *)v20; if ((signed int)v19 >= v24 && (signed int)v19 < *(_WORD *)(v20 + 2) + v24) { v32 = v20 + 4; goto LABEL_39; } v21 = 2; v20 += 8; } v25 = *(_WORD *)v20; if ((signed int)v19 < v25 || (signed int)v19 >= *(_WORD *)(v20 + 2) + v25) { ++v21; v20 += 8; goto LABEL_35; } goto LABEL_48; } } LABEL_38: v32 = error_table; LABEL_39: v33 = v6 + *(_WORD *)(error_table + 12); if (v33) { v35 = v6 + *(_WORD *)(error_table + 12); v36 = 0; do { v37 = v35 % 10; v35 /= 10; v36 += v37; } while (v35); v34 = v36 % 10; } else v34 = 0; snprintf(output, 16, "%s-%d-%1d", v32, v33, v34); return 0; } int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "Usage: ./error error_table.bin 0xC0DE"); return 1; } int *error_table = calloc(1, 1024 * 1024); error_table[2] = calloc(1, 1024 * 1024); error_table[4] = calloc(1, 1024 * 1024); read_error_table(argv[1], error_table); char *str = calloc(1, 500); error_code_to_string(error_table, str, strtoul(argv[2], NULL, 0)); printf("%s\n", str); free(str); free(error_table[2]); free(error_table[4]); free(error_table); return 0; }