Commit a58cb7fd authored by Vitaly Lipatov's avatar Vitaly Lipatov

commit 10.1.0 upon wine-1.3.9

parent 1533cab3
......@@ -820,6 +820,12 @@ SIZE_T WINAPI GlobalSize(HGLOBAL hmem)
if(ISPOINTER(hmem))
{
retval=HeapSize(GetProcessHeap(), 0, hmem);
if (retval == (DWORD)-1) /* It might be a GMEM_MOVEABLE data pointer */
{
retval = HeapSize(GetProcessHeap(), 0, (char*)(hmem) - HGLOBAL_STORAGE);
if (retval != (DWORD)-1) retval -= HGLOBAL_STORAGE;
}
}
else
{
......
......@@ -85,7 +85,7 @@ static void test_heap(void)
UINT flags;
HGLOBAL gbl;
HGLOBAL hsecond;
SIZE_T size;
SIZE_T size, size2;
/* Heap*() functions */
mem = HeapAlloc(GetProcessHeap(), 0, 0);
......@@ -403,6 +403,36 @@ static void test_heap(void)
"MAGIC_DEAD)\n", mem, GetLastError(), GetLastError());
GlobalFree(gbl);
/* trying to get size from data pointer (GMEM_MOVEABLE) */
gbl = GlobalAlloc(GMEM_MOVEABLE, 0x123);
ok(gbl != NULL, "returned NULL\n");
mem = GlobalLock(gbl);
ok(mem != NULL, "returned NULL.\n");
ok(gbl != mem, "unexpectedly equal.\n");
size = GlobalSize(gbl);
size2 = GlobalSize(mem);
ok(size == 0x123, "got %lu\n", size);
ok(size2 == 0x123, "got %lu\n", size2);
GlobalFree(gbl);
/* trying to get size from data pointer (GMEM_FIXED) */
gbl = GlobalAlloc(GMEM_FIXED, 0x123);
ok(gbl != NULL, "returned NULL\n");
mem = GlobalLock(gbl);
ok(mem != NULL, "returned NULL.\n");
ok(gbl == mem, "got %p, %p.\n", gbl, mem);
size = GlobalSize(gbl);
ok(size == 0x123, "got %lu\n", size);
GlobalFree(gbl);
size = GlobalSize((void *)0xdeadbee0);
ok(size == 0, "got %lu\n", size);
}
static void test_obsolete_flags(void)
......
......@@ -480,6 +480,7 @@ HRESULT WINAPI RegisterActiveObject(
HRESULT ret;
LPRUNNINGOBJECTTABLE runobtable;
LPMONIKER moniker;
DWORD rot_flags = ROTFLAGS_REGISTRATIONKEEPSALIVE; /* default registration is strong */
StringFromGUID2(rcid,guidbuf,39);
ret = CreateItemMoniker(pdelimiter,guidbuf,&moniker);
......@@ -490,7 +491,9 @@ HRESULT WINAPI RegisterActiveObject(
IMoniker_Release(moniker);
return ret;
}
ret = IRunningObjectTable_Register(runobtable,dwFlags,punk,moniker,pdwRegister);
if(dwFlags == ACTIVEOBJECT_WEAK)
rot_flags = 0;
ret = IRunningObjectTable_Register(runobtable,rot_flags,punk,moniker,pdwRegister);
IRunningObjectTable_Release(runobtable);
IMoniker_Release(moniker);
return ret;
......
......@@ -215,6 +215,14 @@ static const FMTDATERES VarFormat_date_results[] =
{ 2.725, "hh:nn:ss A/P", "05:24:00 P" }
};
/* The following tests require that the time separator is a colon (:) */
static const FMTDATERES VarFormat_namedtime_results[] =
{
{ 2.525, "short time", "12:36" },
{ 2.525, "medium time", "12:36 PM" },
{ 2.525, "long time", "12:36:00 PM" }
};
#define VNUMFMT(vt,v) \
for (i = 0; i < sizeof(VarFormat_results)/sizeof(FMTRES); i++) \
{ \
......@@ -299,6 +307,23 @@ static void test_VarFormat(void)
VarFormat_date_results[i].res);
}
/* Named time formats */
GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_STIME, buff, sizeof(buff)/sizeof(char));
if (buff[0] != ':' || buff[1])
{
skip("Skipping namedtime tests as time separator is '%s'\n", buff);
}
else
{
for (i = 0; i < sizeof(VarFormat_namedtime_results)/sizeof(FMTDATERES); i++)
{
fd = 0;
VARFMT(VT_DATE,V_DATE,VarFormat_namedtime_results[i].val,
VarFormat_namedtime_results[i].fmt,S_OK,
VarFormat_namedtime_results[i].res);
}
}
/* Strings */
bstrin = SysAllocString(szTesting);
VARFMT(VT_BSTR,V_BSTR,bstrin,"",S_OK,"testing");
......
......@@ -320,48 +320,85 @@ _get_typeinfo_for_iid(REFIID riid, ITypeInfo**ti) {
}
/*
* Determine the number of functions including all inherited functions.
* Determine the number of functions including all inherited functions
* and well as the size of the vtbl.
* Note for non-dual dispinterfaces we simply return the size of IDispatch.
*/
static HRESULT num_of_funcs(ITypeInfo *tinfo, unsigned int *num)
static HRESULT num_of_funcs(ITypeInfo *tinfo, unsigned int *num,
unsigned int *vtbl_size)
{
HRESULT hres;
HRESULT hr;
TYPEATTR *attr;
ITypeInfo *tinfo2;
UINT inherited_funcs = 0, i;
*num = 0;
hres = ITypeInfo_GetTypeAttr(tinfo, &attr);
if (hres) {
ERR("GetTypeAttr failed with %x\n",hres);
return hres;
if(vtbl_size) *vtbl_size = 0;
hr = ITypeInfo_GetTypeAttr(tinfo, &attr);
if (hr)
{
ERR("GetTypeAttr failed with %x\n", hr);
return hr;
}
if(attr->typekind == TKIND_DISPATCH && (attr->wTypeFlags & TYPEFLAG_FDUAL))
if(attr->typekind == TKIND_DISPATCH)
{
if(attr->wTypeFlags & TYPEFLAG_FDUAL)
{
HREFTYPE href;
hres = ITypeInfo_GetRefTypeOfImplType(tinfo, -1, &href);
if(FAILED(hres))
ITypeInfo_ReleaseTypeAttr(tinfo, attr);
hr = ITypeInfo_GetRefTypeOfImplType(tinfo, -1, &href);
if(FAILED(hr))
{
ERR("Unable to get interface href from dual dispinterface\n");
goto end;
return hr;
}
hres = ITypeInfo_GetRefTypeInfo(tinfo, href, &tinfo2);
if(FAILED(hres))
hr = ITypeInfo_GetRefTypeInfo(tinfo, href, &tinfo2);
if(FAILED(hr))
{
ERR("Unable to get interface from dual dispinterface\n");
goto end;
return hr;
}
hres = num_of_funcs(tinfo2, num);
hr = num_of_funcs(tinfo2, num, vtbl_size);
ITypeInfo_Release(tinfo2);
return hr;
}
else
else /* non-dual dispinterface */
{
*num = attr->cbSizeVft / 4;
/* These will be the size of IDispatchVtbl */
*num = attr->cbSizeVft / sizeof(void *);
if(vtbl_size) *vtbl_size = attr->cbSizeVft;
ITypeInfo_ReleaseTypeAttr(tinfo, attr);
return hr;
}
}
for (i = 0; i < attr->cImplTypes; i++)
{
HREFTYPE href;
ITypeInfo *pSubTypeInfo;
UINT sub_funcs;
hr = ITypeInfo_GetRefTypeOfImplType(tinfo, i, &href);
if (FAILED(hr)) goto end;
hr = ITypeInfo_GetRefTypeInfo(tinfo, href, &pSubTypeInfo);
if (FAILED(hr)) goto end;
hr = num_of_funcs(pSubTypeInfo, &sub_funcs, NULL);
ITypeInfo_Release(pSubTypeInfo);
if(FAILED(hr)) goto end;
inherited_funcs += sub_funcs;
}
*num = inherited_funcs + attr->cFuncs;
if(vtbl_size) *vtbl_size = attr->cbSizeVft;
end:
ITypeInfo_ReleaseTypeAttr(tinfo, attr);
return hres;
return hr;
}
#ifdef __i386__
......@@ -1695,7 +1732,7 @@ static HRESULT init_proxy_entry_point(TMProxyImpl *proxy, unsigned int num)
xasm->lret = 0xc2;
xasm->bytestopop = (nrofargs+2)*4; /* pop args, This, iMethod */
xasm->nop = 0x90;
proxy->lpvtbl[num] = xasm;
proxy->lpvtbl[fdesc->oVft / sizeof(void *)] = xasm;
#else
FIXME("not implemented on non i386\n");
return E_FAIL;
......@@ -1710,7 +1747,7 @@ PSFacBuf_CreateProxy(
{
HRESULT hres;
ITypeInfo *tinfo;
unsigned int i, nroffuncs;
unsigned int i, nroffuncs, vtbl_size;
TMProxyImpl *proxy;
TYPEATTR *typeattr;
BOOL defer_to_dispatch = FALSE;
......@@ -1722,7 +1759,9 @@ PSFacBuf_CreateProxy(
return hres;
}
hres = num_of_funcs(tinfo, &nroffuncs);
hres = num_of_funcs(tinfo, &nroffuncs, &vtbl_size);
TRACE("Got %d funcs, vtbl size %d\n", nroffuncs, vtbl_size);
if (FAILED(hres)) {
ERR("Cannot get number of functions for typeinfo %s\n",debugstr_guid(riid));
ITypeInfo_Release(tinfo);
......@@ -1753,7 +1792,7 @@ PSFacBuf_CreateProxy(
InitializeCriticalSection(&proxy->crit);
proxy->crit.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": TMProxyImpl.crit");
proxy->lpvtbl = HeapAlloc(GetProcessHeap(),0,sizeof(LPBYTE)*nroffuncs);
proxy->lpvtbl = HeapAlloc(GetProcessHeap(), 0, vtbl_size);
/* if we derive from IDispatch then defer to its proxy for its methods */
hres = ITypeInfo_GetTypeAttr(tinfo, &typeattr);
......
......@@ -1993,7 +1993,7 @@ MSFT_DoFuncs(TLBContext* pcx,
(*pptfd)->funcdesc.callconv = (pFuncRec->FKCCIC) >> 8 & 0xF;
(*pptfd)->funcdesc.cParams = pFuncRec->nrargs ;
(*pptfd)->funcdesc.cParamsOpt = pFuncRec->nroargs ;
(*pptfd)->funcdesc.oVft = pFuncRec->VtableOffset;
(*pptfd)->funcdesc.oVft = pFuncRec->VtableOffset & ~1;
(*pptfd)->funcdesc.wFuncFlags = LOWORD(pFuncRec->Flags) ;
MSFT_GetTdesc(pcx,
......@@ -3562,7 +3562,7 @@ static void SLTG_DoFuncs(char *pBlk, char *pFirstItem, ITypeInfoImpl *pTI,
(*ppFuncDesc)->funcdesc.callconv = pFunc->nacc & 0x7;
(*ppFuncDesc)->funcdesc.cParams = pFunc->nacc >> 3;
(*ppFuncDesc)->funcdesc.cParamsOpt = (pFunc->retnextopt & 0x7e) >> 1;
(*ppFuncDesc)->funcdesc.oVft = pFunc->vtblpos;
(*ppFuncDesc)->funcdesc.oVft = pFunc->vtblpos & ~1;
if(pFunc->magic & SLTG_FUNCTION_FLAGS_PRESENT)
(*ppFuncDesc)->funcdesc.wFuncFlags = pFunc->funcflags;
......
......@@ -1651,49 +1651,49 @@ HRESULT __RPC_STUB ITypeInfo_Invoke_Stub(
return E_FAIL;
}
HRESULT CALLBACK ITypeInfo_GetDocumentation_Proxy(
ITypeInfo* This,
MEMBERID memid,
BSTR* pBstrName,
BSTR* pBstrDocString,
DWORD* pdwHelpContext,
BSTR* pBstrHelpFile)
HRESULT CALLBACK ITypeInfo_GetDocumentation_Proxy(ITypeInfo *This, MEMBERID memid,
BSTR *name, BSTR *doc_string,
DWORD *help_context, BSTR *help_file)
{
DWORD help_context;
BSTR name, doc_string, help_file;
DWORD dummy_help_context, flags = 0;
BSTR dummy_name, dummy_doc_string, dummy_help_file;
HRESULT hr;
TRACE("(%p, %08x, %p, %p, %p, %p)\n", This, memid, pBstrName, pBstrDocString, pdwHelpContext, pBstrHelpFile);
TRACE("(%p, %08x, %p, %p, %p, %p)\n", This, memid, name, doc_string, help_context, help_file);
/* FIXME: presumably refPtrFlags is supposed to be a bitmask of which ptrs we actually want? */
hr = ITypeInfo_RemoteGetDocumentation_Proxy(This, memid, 0, &name, &doc_string, &help_context, &help_file);
if(SUCCEEDED(hr))
{
if(pBstrName) *pBstrName = name;
else SysFreeString(name);
if(!name) name = &dummy_name;
else flags = 1;
if(pBstrDocString) *pBstrDocString = doc_string;
else SysFreeString(doc_string);
if(!doc_string) doc_string = &dummy_doc_string;
else flags |= 2;
if(pBstrHelpFile) *pBstrHelpFile = help_file;
else SysFreeString(help_file);
if(!help_context) help_context = &dummy_help_context;
else flags |= 4;
if(!help_file) help_file = &dummy_help_file;
else flags |= 8;
hr = ITypeInfo_RemoteGetDocumentation_Proxy(This, memid, flags, name, doc_string, help_context, help_file);
/* We don't need to free the dummy BSTRs since the stub ensures that these will be NULLs. */
if(pdwHelpContext) *pdwHelpContext = help_context;
}
return hr;
}
HRESULT __RPC_STUB ITypeInfo_GetDocumentation_Stub(
ITypeInfo* This,
MEMBERID memid,
DWORD refPtrFlags,
BSTR* pBstrName,
BSTR* pBstrDocString,
DWORD* pdwHelpContext,
BSTR* pBstrHelpFile)
HRESULT __RPC_STUB ITypeInfo_GetDocumentation_Stub(ITypeInfo *This, MEMBERID memid,
DWORD flags, BSTR *name, BSTR *doc_string,
DWORD *help_context, BSTR *help_file)
{
TRACE("(%p, %08x, %08x, %p, %p, %p, %p)\n", This, memid, refPtrFlags, pBstrName, pBstrDocString,
pdwHelpContext, pBstrHelpFile);
return ITypeInfo_GetDocumentation(This, memid, pBstrName, pBstrDocString, pdwHelpContext, pBstrHelpFile);
TRACE("(%p, %08x, %08x, %p, %p, %p, %p)\n", This, memid, flags, name, doc_string, help_context, help_file);
*name = *doc_string = *help_file = NULL;
*help_context = 0;
if(!(flags & 1)) name = NULL;
if(!(flags & 2)) doc_string = NULL;
if(!(flags & 4)) help_context = NULL;
if(!(flags & 8)) help_file = NULL;
return ITypeInfo_GetDocumentation(This, memid, name, doc_string, help_context, help_file);
}
HRESULT CALLBACK ITypeInfo_GetDllEntry_Proxy(
......
......@@ -263,7 +263,7 @@ typedef struct tagFMT_DATE_HEADER
#define FMT_DATE_HOUR_0 0x1F /* Hours with leading 0 */
#define FMT_DATE_HOUR_12 0x20 /* Hours with no leading 0, 12 hour clock */
#define FMT_DATE_HOUR_12_0 0x21 /* Hours with leading 0, 12 hour clock */
#define FMT_DATE_TIME_UNK2 0x23
#define FMT_DATE_TIME_UNK2 0x23 /* same as FMT_DATE_HOUR_0, for "short time" format */
/* FIXME: probably missing some here */
#define FMT_DATE_AMPM_SYS1 0x2E /* AM/PM as defined by system settings */
#define FMT_DATE_AMPM_UPPER 0x2F /* Upper-case AM or PM */
......@@ -1660,6 +1660,13 @@ static HRESULT VARIANT_FormatDate(LPVARIANT pVarIn, LPOLESTR lpszFormat,
pToken += 2;
break;
case FMT_GEN_INLINE:
pToken += 2;
TRACE("copy %s\n", debugstr_a((LPCSTR)pToken));
while (*pToken)
*pBuff++ = *pToken++;
break;
case FMT_DATE_TIME_SEP:
TRACE("time separator\n");
localeValue = LOCALE_STIME;
......@@ -1836,6 +1843,7 @@ static HRESULT VARIANT_FormatDate(LPVARIANT pVarIn, LPOLESTR lpszFormat,
break;
case FMT_DATE_HOUR_0:
case FMT_DATE_TIME_UNK2:
szPrintFmt = szPercentZeroTwo_d;
dwVal = udate.st.wHour;
break;
......
......@@ -1682,6 +1682,7 @@ BOOL WINAPI GetIconInfoExA( HICON icon, ICONINFOEXA *info )
BOOL WINAPI GetIconInfoExW( HICON icon, ICONINFOEXW *info )
{
struct cursoricon_object *ptr;
HMODULE module;
BOOL ret = TRUE;
if (info->cbSize != sizeof(*info))
......@@ -1707,7 +1708,6 @@ BOOL WINAPI GetIconInfoExW( HICON icon, ICONINFOEXW *info )
info->szResName[0] = 0;
if (ptr->module)
{
GetModuleFileNameW( ptr->module, info->szModName, MAX_PATH );
if (IS_INTRESOURCE( ptr->resname )) info->wResID = LOWORD( ptr->resname );
else lstrcpynW( info->szResName, ptr->resname, MAX_PATH );
}
......@@ -1717,7 +1717,9 @@ BOOL WINAPI GetIconInfoExW( HICON icon, ICONINFOEXW *info )
DeleteObject( info->hbmColor );
ret = FALSE;
}
module = ptr->module;
release_icon_ptr( icon, ptr );
if (ret && module) GetModuleFileNameW( module, info->szModName, MAX_PATH );
return ret;
}
......
......@@ -20,6 +20,7 @@
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
......@@ -271,6 +272,12 @@ BOOL PSDRV_WriteSetDownloadFont(PSDRV_PDEVICE *physDev)
/* Retrieve the world -> device transform */
GetTransform(physDev->hdc, 0x204, &xform);
if(GetGraphicsMode(physDev->hdc) == GM_COMPATIBLE)
{
xform.eM11 = xform.eM22 = fabs(xform.eM22);
xform.eM21 = xform.eM12 = 0;
}
physDev->font.size.xx = ps_round(ppem * xform.eM11);
physDev->font.size.xy = ps_round(ppem * xform.eM12);
physDev->font.size.yx = ps_round(ppem * xform.eM21);
......
......@@ -3849,9 +3849,10 @@ static int WS2_sendto( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
{
unsigned int i, options;
int n, fd, err;
struct ws2_async *wsa;
struct ws2_async *wsa = NULL;
int totalLength = 0;
ULONG_PTR cvalue = (lpOverlapped && ((ULONG_PTR)lpOverlapped->hEvent & 1) == 0) ? (ULONG_PTR)lpOverlapped : 0;
DWORD bytes_sent;
TRACE("socket %04lx, wsabuf %p, nbufs %d, flags %d, to %p, tolen %d, ovl %p, func %p\n",
s, lpBuffers, dwBufferCount, dwFlags,
......@@ -3862,6 +3863,11 @@ static int WS2_sendto( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
if ( fd == -1 ) return SOCKET_ERROR;
if (!lpOverlapped && !lpNumberOfBytesSent)
{
err = WSAEFAULT;
goto error;
}
if (!(wsa = HeapAlloc( GetProcessHeap(), 0, FIELD_OFFSET(struct ws2_async, iovec[dwBufferCount]) )))
{
err = WSAEFAULT;
......@@ -3883,12 +3889,6 @@ static int WS2_sendto( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
totalLength += lpBuffers[i].len;
}
if (!lpNumberOfBytesSent)
{
err = WSAEFAULT;
goto error;
}
for (;;)
{
n = WS2_send( fd, wsa );
......@@ -3936,7 +3936,7 @@ static int WS2_sendto( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
iosb->u.Status = STATUS_SUCCESS;
iosb->Information = n;
*lpNumberOfBytesSent = n;
if (lpNumberOfBytesSent) *lpNumberOfBytesSent = n;
if (!wsa->completion_func)
{
if (cvalue) WS_AddCompletion( s, cvalue, STATUS_SUCCESS, n );
......@@ -3955,7 +3955,7 @@ static int WS2_sendto( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
* sending blocks until the entire buffer is sent. */
DWORD timeout_start = GetTickCount();
*lpNumberOfBytesSent = 0;
bytes_sent = 0;
while (wsa->first_iovec < dwBufferCount)
{
......@@ -3964,7 +3964,7 @@ static int WS2_sendto( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
if (n >= 0)
{
*lpNumberOfBytesSent += n;
bytes_sent += n;
while (wsa->first_iovec < dwBufferCount && wsa->iovec[wsa->first_iovec].iov_len <= n)
n -= wsa->iovec[wsa->first_iovec++].iov_len;
if (wsa->first_iovec >= dwBufferCount) break;
......@@ -4004,11 +4004,12 @@ static int WS2_sendto( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
err = WSAEWOULDBLOCK;
goto error;
}
*lpNumberOfBytesSent = n;
bytes_sent = n;
}
TRACE(" -> %i bytes\n", *lpNumberOfBytesSent);
TRACE(" -> %i bytes\n", bytes_sent);
if (lpNumberOfBytesSent) *lpNumberOfBytesSent = bytes_sent;
HeapFree( GetProcessHeap(), 0, wsa );
release_sock_fd( s, fd );
WSASetLastError(0);
......
......@@ -1520,8 +1520,12 @@ static unsigned int write_nonsimple_pointer(FILE *file, const attr_list_t *attrs
if (out_attr && !in_attr && pointer_type == RPC_FC_RP)
flags |= RPC_FC_P_ONSTACK;
if (is_ptr(type) && is_declptr(type_pointer_get_ref(type)))
if (is_ptr(type))
{
type_t *ref = type_pointer_get_ref(type);
if(is_declptr(ref) && !is_user_type(ref))
flags |= RPC_FC_P_DEREF;
}
print_file(file, 2, "0x%x, 0x%x,\t\t/* %s",
pointer_type,
......@@ -4030,10 +4034,22 @@ void assign_stub_out_args( FILE *file, int indent, const var_t *func, const char
if (type_array_has_conformance(var->type))
{
unsigned int size;
type_t *type = var->type;
type_t *type;
fprintf(file, " = NdrAllocate(&__frame->_StubMsg, ");
for ( ;
for (type = var->type;
is_array(type) && type_array_has_conformance(type);
type = type_array_get_element(type))
{
write_expr(file, type_array_get_conformance(type), TRUE,
TRUE, NULL, NULL, local_var_prefix);
fprintf(file, " * ");
}
size = type_memsize(type);
fprintf(file, "%u);\n", size);
print_file(file, indent, "memset(%s%s, 0, ", local_var_prefix, var->name);
for (type = var->type;
is_array(type) && type_array_has_conformance(type);
type = type_array_get_element(type))
{
......@@ -4055,12 +4071,15 @@ void assign_stub_out_args( FILE *file, int indent, const var_t *func, const char
case TGT_ENUM:
case TGT_POINTER:
case TGT_RANGE:
case TGT_IFACE_POINTER:
print_file(file, indent, "%s_W%u = 0;\n", local_var_prefix, i);
break;
case TGT_USER_TYPE:
print_file(file, indent, "memset(&%s_W%u, 0, sizeof(%s_W%u));\n",
local_var_prefix, i, local_var_prefix, i);
break;
case TGT_STRUCT:
case TGT_UNION:
case TGT_USER_TYPE:
case TGT_IFACE_POINTER:
case TGT_ARRAY:
case TGT_CTXT_HANDLE:
case TGT_CTXT_HANDLE_POINTER:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment