Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-winehq
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-winehq
Commits
7b012817
Commit
7b012817
authored
May 13, 2020
by
Alexandre Julliard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
kernelbase: Reimplement FormatMessageA/W using RtlFormatMessage().
Signed-off-by:
Alexandre Julliard
<
julliard@winehq.org
>
parent
3214ca84
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
206 additions
and
27 deletions
+206
-27
Makefile.in
dlls/kernel32/Makefile.in
+0
-1
format_msg.c
dlls/kernel32/format_msg.c
+0
-0
kernel32.spec
dlls/kernel32/kernel32.spec
+2
-2
format_msg.c
dlls/kernel32/tests/format_msg.c
+11
-22
kernelbase.spec
dlls/kernelbase/kernelbase.spec
+2
-2
locale.c
dlls/kernelbase/locale.c
+191
-0
No files found.
dlls/kernel32/Makefile.in
View file @
7b012817
...
...
@@ -16,7 +16,6 @@ C_SRCS = \
editline.c
\
environ.c
\
file.c
\
format_msg.c
\
heap.c
\
kernel_main.c
\
lcformat.c
\
...
...
dlls/kernel32/format_msg.c
deleted
100644 → 0
View file @
3214ca84
This diff is collapsed.
Click to expand it.
dlls/kernel32/kernel32.spec
View file @
7b012817
...
...
@@ -522,8 +522,8 @@
@ stdcall -import FlushViewOfFile(ptr long)
@ stdcall FoldStringA(long str long ptr long)
@ stdcall -import FoldStringW(long wstr long ptr long)
@ stdcall FormatMessageA(long ptr long long ptr long ptr)
@ stdcall FormatMessageW(long ptr long long ptr long ptr)
@ stdcall
-import
FormatMessageA(long ptr long long ptr long ptr)
@ stdcall
-import
FormatMessageW(long ptr long long ptr long ptr)
@ stdcall FreeConsole()
@ stdcall -import FreeEnvironmentStringsA(ptr)
@ stdcall -import FreeEnvironmentStringsW(ptr)
...
...
dlls/kernel32/tests/format_msg.c
View file @
7b012817
...
...
@@ -77,7 +77,7 @@ static void test_message_from_string_wide(void)
error
=
GetLastError
();
ok
(
!
lstrcmpW
(
L""
,
out
),
"failed out=%s
\n
"
,
wine_dbgstr_w
(
out
));
ok
(
r
==
0
,
"succeeded: r=%d
\n
"
,
r
);
ok
(
(
error
==
0xdeadbeef
)
||
(
error
==
ERROR_NO_WORK_DONE
),
"last error %u
\n
"
,
error
);
ok
(
error
==
ERROR_NO_WORK_DONE
||
broken
(
error
==
0xdeadbeef
),
"last error %u
\n
"
,
error
);
/* format placeholder with no specifier */
SetLastError
(
0xdeadbeef
);
...
...
@@ -94,8 +94,7 @@ static void test_message_from_string_wide(void)
lstrcpyW
(
out
,
L"xxxxxx"
);
r
=
FormatMessageW
(
FORMAT_MESSAGE_FROM_STRING
,
L"test%"
,
0
,
0
,
out
,
ARRAY_SIZE
(
out
),
NULL
);
error
=
GetLastError
();
ok
(
!
lstrcmpW
(
out
,
L"xxxxxx"
)
||
broken
(
!
lstrcmpW
(
out
,
L"testxx"
)),
/* W2K3+ */
ok
(
!
lstrcmpW
(
out
,
L"testxx"
)
||
broken
(
!
lstrcmpW
(
out
,
L"xxxxxx"
)),
/* winxp */
"Expected the buffer to be unchanged
\n
"
);
ok
(
r
==
0
,
"succeeded: r=%d
\n
"
,
r
);
ok
(
error
==
ERROR_INVALID_PARAMETER
,
"last error %u
\n
"
,
error
);
...
...
@@ -359,7 +358,7 @@ static void test_message_from_string(void)
r
=
FormatMessageA
(
FORMAT_MESSAGE_FROM_STRING
,
""
,
0
,
0
,
out
,
ARRAY_SIZE
(
out
),
NULL
);
ok
(
!
memcmp
(
out
,
init_buf
,
sizeof
(
init_buf
)),
"Expected the buffer to be untouched
\n
"
);
ok
(
r
==
0
,
"succeeded: r=%d
\n
"
,
r
);
ok
(
(
GetLastError
()
==
0xdeadbeef
)
||
(
GetLastError
()
==
ERROR_NO_WORK_DONE
),
ok
(
GetLastError
()
==
ERROR_NO_WORK_DONE
||
broken
(
GetLastError
()
==
0xdeadbeef
),
"last error %u
\n
"
,
GetLastError
());
/* format placeholder with no specifier */
...
...
@@ -661,8 +660,8 @@ static void test_message_ignore_inserts(void)
ARRAY_SIZE
(
out
),
NULL
);
ok
(
ret
==
0
,
"Expected FormatMessageA to return 0, got %d
\n
"
,
ret
);
ok
(
!
memcmp
(
out
,
init_buf
,
sizeof
(
init_buf
)),
"Expected the output buffer to be untouched
\n
"
);
ok
(
(
GetLastError
()
==
0xdeadbeef
)
||
(
GetLastError
()
==
ERROR_NO_WORK_DONE
),
"Expected GetLastError() to return
0xdeadbeef or
ERROR_NO_WORK_DONE, got %u
\n
"
,
GetLastError
());
ok
(
GetLastError
()
==
ERROR_NO_WORK_DONE
||
broken
(
GetLastError
()
==
0xdeadbeef
),
"Expected GetLastError() to return ERROR_NO_WORK_DONE, got %u
\n
"
,
GetLastError
());
/* Insert sequences are ignored. */
ret
=
FormatMessageA
(
FORMAT_MESSAGE_FROM_STRING
|
FORMAT_MESSAGE_IGNORE_INSERTS
,
"test%1%2!*.*s!%99"
,
0
,
0
,
out
,
...
...
@@ -751,8 +750,8 @@ static void test_message_ignore_inserts_wide(void)
ARRAY_SIZE
(
out
),
NULL
);
ok
(
ret
==
0
,
"Expected FormatMessageW to return 0, got %d
\n
"
,
ret
);
ok
(
!
lstrcmpW
(
L""
,
out
),
"Expected the output buffer to be the empty string, got %s
\n
"
,
wine_dbgstr_w
(
out
));
ok
(
(
GetLastError
()
==
0xdeadbeef
)
||
(
GetLastError
()
==
ERROR_NO_WORK_DONE
),
"Expected GetLastError() to return
0xdeadbeef or
ERROR_NO_WORK_DONE, got %u
\n
"
,
GetLastError
());
ok
(
GetLastError
()
==
ERROR_NO_WORK_DONE
||
broken
(
GetLastError
()
==
0xdeadbeef
),
"Expected GetLastError() to return ERROR_NO_WORK_DONE, got %u
\n
"
,
GetLastError
());
/* Insert sequences are ignored. */
ret
=
FormatMessageW
(
FORMAT_MESSAGE_FROM_STRING
|
FORMAT_MESSAGE_IGNORE_INSERTS
,
L"test%1%2!*.*s!%99"
,
0
,
0
,
out
,
...
...
@@ -1066,7 +1065,6 @@ static void test_message_insufficient_buffer_wide(void)
ok
(
GetLastError
()
==
ERROR_INSUFFICIENT_BUFFER
,
"Expected GetLastError() to return ERROR_INSUFFICIENT_BUFFER, got %u
\n
"
,
GetLastError
());
todo_wine
ok
(
!
memcmp
(
out
,
L"
\0
xxxxx"
,
6
*
sizeof
(
WCHAR
))
||
broken
(
!
lstrcmpW
(
out
,
L"xxxxxx"
)),
/* winxp */
"Expected the buffer to be truncated
\n
"
);
...
...
@@ -1078,7 +1076,6 @@ static void test_message_insufficient_buffer_wide(void)
ok
(
GetLastError
()
==
ERROR_INSUFFICIENT_BUFFER
,
"Expected GetLastError() to return ERROR_INSUFFICIENT_BUFFER, got %u
\n
"
,
GetLastError
());
todo_wine
ok
(
!
memcmp
(
out
,
L"tes
\0
xx"
,
6
*
sizeof
(
WCHAR
))
||
broken
(
!
lstrcmpW
(
out
,
L"xxxxxx"
)),
/* winxp */
"Expected the buffer to be truncated
\n
"
);
...
...
@@ -1185,8 +1182,8 @@ static void test_message_allocate_buffer(void)
""
,
0
,
0
,
(
char
*
)
&
buf
,
0
,
NULL
);
ok
(
ret
==
0
,
"Expected FormatMessageA to return 0, got %u
\n
"
,
ret
);
ok
(
buf
==
NULL
,
"Expected output buffer pointer to be NULL
\n
"
);
ok
(
(
GetLastError
()
==
0xdeadbeef
)
||
(
GetLastError
()
==
ERROR_NO_WORK_DONE
),
"Expected GetLastError() to return
0xdeadbeef or
ERROR_NO_WORK_DONE, got %u
\n
"
,
GetLastError
());
ok
(
GetLastError
()
==
ERROR_NO_WORK_DONE
||
broken
(
GetLastError
()
==
0xdeadbeef
),
"Expected GetLastError() to return ERROR_NO_WORK_DONE, got %u
\n
"
,
GetLastError
());
buf
=
(
char
*
)
0xdeadbeef
;
ret
=
FormatMessageA
(
FORMAT_MESSAGE_FROM_STRING
|
FORMAT_MESSAGE_ALLOCATE_BUFFER
,
...
...
@@ -1279,8 +1276,8 @@ static void test_message_allocate_buffer_wide(void)
L""
,
0
,
0
,
(
WCHAR
*
)
&
buf
,
0
,
NULL
);
ok
(
ret
==
0
,
"Expected FormatMessageW to return 0, got %u
\n
"
,
ret
);
ok
(
buf
==
NULL
,
"Expected output buffer pointer to be NULL
\n
"
);
ok
(
(
GetLastError
()
==
0xdeadbeef
)
||
(
GetLastError
()
==
ERROR_NO_WORK_DONE
),
"Expected GetLastError() to return
0xdeadbeef or
ERROR_NO_WORK_DONE, got %u
\n
"
,
GetLastError
());
ok
(
GetLastError
()
==
ERROR_NO_WORK_DONE
||
broken
(
GetLastError
()
==
0xdeadbeef
),
"Expected GetLastError() to return ERROR_NO_WORK_DONE, got %u
\n
"
,
GetLastError
());
buf
=
(
WCHAR
*
)
0xdeadbeef
;
ret
=
FormatMessageW
(
FORMAT_MESSAGE_FROM_STRING
|
FORMAT_MESSAGE_ALLOCATE_BUFFER
,
...
...
@@ -1637,18 +1634,14 @@ static void test_message_from_64bit_number(void)
r
=
doitW
(
FORMAT_MESSAGE_FROM_STRING
,
L"%1!I64u!"
,
0
,
0
,
outW
,
ARRAY_SIZE
(
outW
),
unsigned_tests
[
i
].
number
);
MultiByteToWideChar
(
CP_ACP
,
0
,
unsigned_tests
[
i
].
expected
,
-
1
,
expW
,
ARRAY_SIZE
(
expW
));
todo_wine
{
ok
(
!
lstrcmpW
(
outW
,
expW
),
"[%d] failed, expected %s, got %s
\n
"
,
i
,
unsigned_tests
[
i
].
expected
,
wine_dbgstr_w
(
outW
));
ok
(
r
==
unsigned_tests
[
i
].
len
,
"[%d] failed: r=%d
\n
"
,
i
,
r
);
}
r
=
doit
(
FORMAT_MESSAGE_FROM_STRING
,
"%1!I64u!"
,
0
,
0
,
outA
,
sizeof
(
outA
),
unsigned_tests
[
i
].
number
);
todo_wine
{
ok
(
!
strcmp
(
outA
,
unsigned_tests
[
i
].
expected
),
"[%d] failed, expected %s, got %s
\n
"
,
i
,
unsigned_tests
[
i
].
expected
,
outA
);
ok
(
r
==
unsigned_tests
[
i
].
len
,
"[%d] failed: r=%d
\n
"
,
i
,
r
);
}
}
for
(
i
=
0
;
i
<
ARRAY_SIZE
(
signed_tests
);
i
++
)
...
...
@@ -1656,18 +1649,14 @@ todo_wine {
r
=
doitW
(
FORMAT_MESSAGE_FROM_STRING
,
L"%1!I64d!"
,
0
,
0
,
outW
,
ARRAY_SIZE
(
outW
),
signed_tests
[
i
].
number
);
MultiByteToWideChar
(
CP_ACP
,
0
,
signed_tests
[
i
].
expected
,
-
1
,
expW
,
ARRAY_SIZE
(
expW
));
todo_wine
{
ok
(
!
lstrcmpW
(
outW
,
expW
),
"[%d] failed, expected %s, got %s
\n
"
,
i
,
signed_tests
[
i
].
expected
,
wine_dbgstr_w
(
outW
));
ok
(
r
==
signed_tests
[
i
].
len
,
"[%d] failed: r=%d
\n
"
,
i
,
r
);
}
r
=
doit
(
FORMAT_MESSAGE_FROM_STRING
,
"%1!I64d!"
,
0
,
0
,
outA
,
sizeof
(
outA
),
signed_tests
[
i
].
number
);
todo_wine
{
ok
(
!
strcmp
(
outA
,
signed_tests
[
i
].
expected
),
"[%d] failed, expected %s, got %s
\n
"
,
i
,
signed_tests
[
i
].
expected
,
outA
);
ok
(
r
==
signed_tests
[
i
].
len
,
"[%d] failed: r=%d
\n
"
,
i
,
r
);
}
}
}
...
...
dlls/kernelbase/kernelbase.spec
View file @
7b012817
...
...
@@ -387,8 +387,8 @@
@ stdcall FoldStringW(long wstr long ptr long)
# @ stub ForceSyncFgPolicyInternal
# @ stub FormatApplicationUserModelId
@ stdcall FormatMessageA(long ptr long long ptr long ptr)
kernel32.FormatMessageA
@ stdcall FormatMessageW(long ptr long long ptr long ptr)
kernel32.FormatMessageW
@ stdcall FormatMessageA(long ptr long long ptr long ptr)
@ stdcall FormatMessageW(long ptr long long ptr long ptr)
@ stdcall FreeConsole()
@ stdcall FreeEnvironmentStringsA(ptr) FreeEnvironmentStringsW
@ stdcall FreeEnvironmentStringsW(ptr)
...
...
dlls/kernelbase/locale.c
View file @
7b012817
...
...
@@ -3639,6 +3639,197 @@ INT WINAPI DECLSPEC_HOTPATCH FoldStringW( DWORD flags, LPCWSTR src, INT srclen,
}
static
const
WCHAR
*
get_message
(
DWORD
flags
,
const
void
*
src
,
UINT
id
,
UINT
lang
,
BOOL
ansi
,
WCHAR
**
buffer
)
{
DWORD
len
;
if
(
!
(
flags
&
FORMAT_MESSAGE_FROM_STRING
))
{
const
MESSAGE_RESOURCE_ENTRY
*
entry
;
NTSTATUS
status
=
STATUS_INVALID_PARAMETER
;
if
(
flags
&
FORMAT_MESSAGE_FROM_HMODULE
)
{
HMODULE
module
=
(
HMODULE
)
src
;
if
(
!
module
)
module
=
GetModuleHandleW
(
0
);
status
=
RtlFindMessage
(
module
,
RT_MESSAGETABLE
,
lang
,
id
,
&
entry
);
}
if
(
status
&&
(
flags
&
FORMAT_MESSAGE_FROM_SYSTEM
))
{
/* Fold win32 hresult to its embedded error code. */
if
(
HRESULT_SEVERITY
(
id
)
==
SEVERITY_ERROR
&&
HRESULT_FACILITY
(
id
)
==
FACILITY_WIN32
)
id
=
HRESULT_CODE
(
id
);
status
=
RtlFindMessage
(
kernel32_handle
,
RT_MESSAGETABLE
,
lang
,
id
,
&
entry
);
}
if
(
!
set_ntstatus
(
status
))
return
NULL
;
src
=
entry
->
Text
;
ansi
=
!
(
entry
->
Flags
&
MESSAGE_RESOURCE_UNICODE
);
}
if
(
!
ansi
)
return
src
;
len
=
MultiByteToWideChar
(
CP_ACP
,
0
,
src
,
-
1
,
NULL
,
0
);
if
(
!
(
*
buffer
=
HeapAlloc
(
GetProcessHeap
(),
0
,
len
*
sizeof
(
WCHAR
)
)))
return
NULL
;
MultiByteToWideChar
(
CP_ACP
,
0
,
src
,
-
1
,
*
buffer
,
len
);
return
*
buffer
;
}
/***********************************************************************
* FormatMessageA (kernelbase.@)
*/
DWORD
WINAPI
DECLSPEC_HOTPATCH
FormatMessageA
(
DWORD
flags
,
const
void
*
source
,
DWORD
msgid
,
DWORD
langid
,
char
*
buffer
,
DWORD
size
,
__ms_va_list
*
args
)
{
DWORD
ret
=
0
;
ULONG
len
,
retsize
=
0
;
ULONG
width
=
(
flags
&
FORMAT_MESSAGE_MAX_WIDTH_MASK
);
const
WCHAR
*
src
;
WCHAR
*
result
,
*
message
=
NULL
;
NTSTATUS
status
;
TRACE
(
"(0x%x,%p,%d,0x%x,%p,%d,%p)
\n
"
,
flags
,
source
,
msgid
,
langid
,
buffer
,
size
,
args
);
if
(
flags
&
FORMAT_MESSAGE_ALLOCATE_BUFFER
)
{
if
(
!
buffer
)
{
SetLastError
(
ERROR_NOT_ENOUGH_MEMORY
);
return
0
;
}
*
(
char
**
)
buffer
=
NULL
;
}
if
(
size
>=
32768
)
{
SetLastError
(
ERROR_INVALID_PARAMETER
);
return
0
;
}
if
(
width
==
0xff
)
width
=
~
0u
;
if
(
!
(
src
=
get_message
(
flags
,
source
,
msgid
,
langid
,
TRUE
,
&
message
)))
return
0
;
if
(
!
(
result
=
HeapAlloc
(
GetProcessHeap
(),
0
,
65536
)))
status
=
STATUS_NO_MEMORY
;
else
status
=
RtlFormatMessage
(
src
,
width
,
!!
(
flags
&
FORMAT_MESSAGE_IGNORE_INSERTS
),
TRUE
,
!!
(
flags
&
FORMAT_MESSAGE_ARGUMENT_ARRAY
),
args
,
result
,
65536
,
&
retsize
);
HeapFree
(
GetProcessHeap
(),
0
,
message
);
if
(
status
==
STATUS_BUFFER_OVERFLOW
)
{
SetLastError
(
ERROR_INSUFFICIENT_BUFFER
);
goto
done
;
}
if
(
!
set_ntstatus
(
status
))
goto
done
;
len
=
WideCharToMultiByte
(
CP_ACP
,
0
,
result
,
retsize
/
sizeof
(
WCHAR
),
NULL
,
0
,
NULL
,
NULL
);
if
(
len
<=
1
)
{
SetLastError
(
ERROR_NO_WORK_DONE
);
goto
done
;
}
if
(
flags
&
FORMAT_MESSAGE_ALLOCATE_BUFFER
)
{
char
*
buf
=
LocalAlloc
(
LMEM_ZEROINIT
,
max
(
size
,
len
));
if
(
!
buf
)
{
SetLastError
(
ERROR_NOT_ENOUGH_MEMORY
);
goto
done
;
}
*
(
char
**
)
buffer
=
buf
;
WideCharToMultiByte
(
CP_ACP
,
0
,
result
,
retsize
/
sizeof
(
WCHAR
),
buf
,
max
(
size
,
len
),
NULL
,
NULL
);
}
else
if
(
len
>
size
)
{
SetLastError
(
ERROR_INSUFFICIENT_BUFFER
);
goto
done
;
}
else
WideCharToMultiByte
(
CP_ACP
,
0
,
result
,
retsize
/
sizeof
(
WCHAR
),
buffer
,
size
,
NULL
,
NULL
);
ret
=
len
-
1
;
done:
HeapFree
(
GetProcessHeap
(),
0
,
result
);
return
ret
;
}
/***********************************************************************
* FormatMessageW (kernelbase.@)
*/
DWORD
WINAPI
DECLSPEC_HOTPATCH
FormatMessageW
(
DWORD
flags
,
const
void
*
source
,
DWORD
msgid
,
DWORD
langid
,
WCHAR
*
buffer
,
DWORD
size
,
__ms_va_list
*
args
)
{
ULONG
retsize
=
0
;
ULONG
width
=
(
flags
&
FORMAT_MESSAGE_MAX_WIDTH_MASK
);
const
WCHAR
*
src
;
WCHAR
*
message
=
NULL
;
NTSTATUS
status
;
TRACE
(
"(0x%x,%p,%d,0x%x,%p,%d,%p)
\n
"
,
flags
,
source
,
msgid
,
langid
,
buffer
,
size
,
args
);
if
(
!
buffer
)
{
SetLastError
(
ERROR_INVALID_PARAMETER
);
return
0
;
}
if
(
width
==
0xff
)
width
=
~
0u
;
if
(
flags
&
FORMAT_MESSAGE_ALLOCATE_BUFFER
)
*
(
LPWSTR
*
)
buffer
=
NULL
;
if
(
!
(
src
=
get_message
(
flags
,
source
,
msgid
,
langid
,
FALSE
,
&
message
)))
return
0
;
if
(
flags
&
FORMAT_MESSAGE_ALLOCATE_BUFFER
)
{
WCHAR
*
result
;
ULONG
alloc
=
max
(
size
*
sizeof
(
WCHAR
),
65536
);
for
(;;)
{
if
(
!
(
result
=
HeapAlloc
(
GetProcessHeap
(),
0
,
alloc
)))
{
status
=
STATUS_NO_MEMORY
;
break
;
}
status
=
RtlFormatMessage
(
src
,
width
,
!!
(
flags
&
FORMAT_MESSAGE_IGNORE_INSERTS
),
FALSE
,
!!
(
flags
&
FORMAT_MESSAGE_ARGUMENT_ARRAY
),
args
,
result
,
alloc
,
&
retsize
);
if
(
!
status
)
{
if
(
retsize
<=
sizeof
(
WCHAR
))
HeapFree
(
GetProcessHeap
(),
0
,
result
);
else
*
(
WCHAR
**
)
buffer
=
HeapReAlloc
(
GetProcessHeap
(),
HEAP_REALLOC_IN_PLACE_ONLY
,
result
,
max
(
retsize
,
size
*
sizeof
(
WCHAR
)
));
break
;
}
HeapFree
(
GetProcessHeap
(),
0
,
result
);
if
(
status
!=
STATUS_BUFFER_OVERFLOW
)
break
;
alloc
*=
2
;
}
}
else
status
=
RtlFormatMessage
(
src
,
width
,
!!
(
flags
&
FORMAT_MESSAGE_IGNORE_INSERTS
),
FALSE
,
!!
(
flags
&
FORMAT_MESSAGE_ARGUMENT_ARRAY
),
args
,
buffer
,
size
*
sizeof
(
WCHAR
),
&
retsize
);
HeapFree
(
GetProcessHeap
(),
0
,
message
);
if
(
status
==
STATUS_BUFFER_OVERFLOW
)
{
if
(
size
)
buffer
[
size
-
1
]
=
0
;
SetLastError
(
ERROR_INSUFFICIENT_BUFFER
);
return
0
;
}
if
(
!
set_ntstatus
(
status
))
return
0
;
if
(
retsize
<=
sizeof
(
WCHAR
))
SetLastError
(
ERROR_NO_WORK_DONE
);
return
retsize
/
sizeof
(
WCHAR
)
-
1
;
}
/******************************************************************************
* GetACP (kernelbase.@)
*/
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment