Commit 0a8114c1 authored by Alexandre Julliard's avatar Alexandre Julliard

Raise an exception for unimplemented 16-bit entry points too.

Added check for duplicate names in 16-bit spec files.
parent 0ba2b569
...@@ -422,7 +422,7 @@ owner kernel32 ...@@ -422,7 +422,7 @@ owner kernel32
#508 stub WOWFAILEDEXEC # conflict with 507 ! (something broken here ?) #508 stub WOWFAILEDEXEC # conflict with 507 ! (something broken here ?)
508 stub WOWCLOSECOMPORT 508 stub WOWCLOSECOMPORT
#509 stub WOWCLOSECOMPORT # conflict with 508 ! #509 stub WOWCLOSECOMPORT # conflict with 508 !
509 stub WOWKILLREMOTETASK #509 stub WOWKILLREMOTETASK
511 stub WOWKILLREMOTETASK 511 stub WOWKILLREMOTETASK
512 stub WOWQUERYDEBUG 512 stub WOWQUERYDEBUG
513 pascal LoadLibraryEx32W(ptr long long) LoadLibraryEx32W16 # Both NT/95 513 pascal LoadLibraryEx32W(ptr long long) LoadLibraryEx32W16 # Both NT/95
...@@ -482,12 +482,16 @@ owner kernel32 ...@@ -482,12 +482,16 @@ owner kernel32
602 pascal16 GetDummyModuleHandleDS() GetDummyModuleHandleDS16 602 pascal16 GetDummyModuleHandleDS() GetDummyModuleHandleDS16
603 stub KERNEL_603 # OutputDebugString (?) 603 stub KERNEL_603 # OutputDebugString (?)
604 register CBClientGlueSL() CBClientGlueSL 604 register CBClientGlueSL() CBClientGlueSL
605 pascal AllocSLThunkletCallback(long long) AllocSLThunkletCallback16 # FIXME: 605 is duplicate of 562
606 pascal AllocLSThunkletCallback(segptr long) AllocLSThunkletCallback16 605 pascal AllocSLThunkletCallback_dup(long long) AllocSLThunkletCallback16
# FIXME: 606 is duplicate of 561
606 pascal AllocLSThunkletCallback_dup(segptr long) AllocLSThunkletCallback16
607 pascal AllocLSThunkletSysthunk(segptr long long) AllocLSThunkletSysthunk16 607 pascal AllocLSThunkletSysthunk(segptr long long) AllocLSThunkletSysthunk16
608 pascal AllocSLThunkletSysthunk(long segptr long) AllocSLThunkletSysthunk16 608 pascal AllocSLThunkletSysthunk(long segptr long) AllocSLThunkletSysthunk16
609 pascal FindLSThunkletCallback(segptr long) FindLSThunkletCallback # FIXME: 609 is duplicate of 563
610 pascal FindSLThunkletCallback(long long) FindSLThunkletCallback 609 pascal FindLSThunkletCallback_dup(segptr long) FindLSThunkletCallback
# FIXME: 610 is duplicate of 562
610 pascal FindSLThunkletCallback_dup(long long) FindSLThunkletCallback
611 pascal16 FreeThunklet(long long) FreeThunklet16 611 pascal16 FreeThunklet(long long) FreeThunklet16
612 pascal16 IsSLThunklet(ptr) IsSLThunklet16 612 pascal16 IsSLThunklet(ptr) IsSLThunklet16
613 stub HugeMapLS 613 stub HugeMapLS
......
...@@ -169,14 +169,16 @@ owner ole32 ...@@ -169,14 +169,16 @@ owner ole32
166 stub OPDELETE16 166 stub OPDELETE16
167 stub ?GETSIZEVALUE@CARRAYFVALUE@@RFCHXZ 167 stub ?GETSIZEVALUE@CARRAYFVALUE@@RFCHXZ
168 stub ?PROXY1632ADDREF@@ZAKPEVCPROXY1632@@@Z 168 stub ?PROXY1632ADDREF@@ZAKPEVCPROXY1632@@@Z
169 stub REMLOOKUPSHUNK # FIXME: 169 is a duplicate of 97
169 stub REMLOOKUPSHUNK_dup
170 stub ?ISEMPTY@CMAPKEYTOVALUE@@RFCHXZ 170 stub ?ISEMPTY@CMAPKEYTOVALUE@@RFCHXZ
171 stub ?FREE@CSTDMALLOC@@VEAXPEX@Z 171 stub ?FREE@CSTDMALLOC@@VEAXPEX@Z
172 stub CALLTHKMGRINITIALIZE 172 stub CALLTHKMGRINITIALIZE
173 stub ?REALLOC@CSTDMALLOC@@VEAPEXPEXK@Z 173 stub ?REALLOC@CSTDMALLOC@@VEAPEXPEXK@Z
174 stub ?SM16RHQI@@ZAPEXPEVCSM16RELEASEHANDLER@@AFUGUID@@PEPEX@Z 174 stub ?SM16RHQI@@ZAPEXPEVCSM16RELEASEHANDLER@@AFUGUID@@PEPEX@Z
175 stub ?PROXY1632METHOD10@@ZAKPEVCPROXY1632@@@Z 175 stub ?PROXY1632METHOD10@@ZAKPEVCPROXY1632@@@Z
176 stub ___EXPORTEDSTUB # FIXME: 176 is a duplicate of 154
176 stub ___EXPORTEDSTUB_dup
177 stub ?PROXY1632METHOD20@@ZAKPEVCPROXY1632@@@Z 177 stub ?PROXY1632METHOD20@@ZAKPEVCPROXY1632@@@Z
178 stub ?PROXY1632METHOD11@@ZAKPEVCPROXY1632@@@Z 178 stub ?PROXY1632METHOD11@@ZAKPEVCPROXY1632@@@Z
179 stub ?PROXY1632METHOD30@@ZAKPEVCPROXY1632@@@Z 179 stub ?PROXY1632METHOD30@@ZAKPEVCPROXY1632@@@Z
......
...@@ -263,24 +263,6 @@ void RELAY_DebugCallFrom16Ret( CONTEXT86 *context, int ret_val ) ...@@ -263,24 +263,6 @@ void RELAY_DebugCallFrom16Ret( CONTEXT86 *context, int ret_val )
/*********************************************************************** /***********************************************************************
* RELAY_Unimplemented16
*
* This function is called for unimplemented 16-bit entry points (declared
* as 'stub' in the spec file).
*/
void RELAY_Unimplemented16(void)
{
WORD ordinal;
char name[80];
STACK16FRAME *frame = CURRENT_STACK16;
BUILTIN_GetEntryPoint16( frame, name, &ordinal );
MESSAGE("FATAL: No handler for Win16 routine %s (called from %04x:%04x)\n",
name, frame->cs, frame->ip );
ExitProcess(1);
}
/***********************************************************************
* RELAY_DebugCallTo16 * RELAY_DebugCallTo16
* *
* 'target' contains either the function to call (normal CallTo16) * 'target' contains either the function to call (normal CallTo16)
......
...@@ -409,7 +409,6 @@ void TASK_ExitTask(void) ...@@ -409,7 +409,6 @@ void TASK_ExitTask(void)
if (!nTaskCount || (nTaskCount == 1 && hFirstTask == initial_task)) if (!nTaskCount || (nTaskCount == 1 && hFirstTask == initial_task))
{ {
TRACE("this is the last task, exiting\n" ); TRACE("this is the last task, exiting\n" );
ERR("done\n");
ExitKernel16(); ExitKernel16();
} }
......
...@@ -21,7 +21,8 @@ struct import ...@@ -21,7 +21,8 @@ struct import
}; };
static char **undef_symbols; /* list of undefined symbols */ static char **undef_symbols; /* list of undefined symbols */
static int nb_undef_symbols, undef_size; static int nb_undef_symbols = -1;
static int undef_size;
static struct import **dll_imports = NULL; static struct import **dll_imports = NULL;
static int nb_imports = 0; /* number of imported dlls */ static int nb_imports = 0; /* number of imported dlls */
...@@ -223,7 +224,7 @@ int resolve_imports( FILE *outfile ) ...@@ -223,7 +224,7 @@ int resolve_imports( FILE *outfile )
int i, j, off; int i, j, off;
char **p; char **p;
if (!nb_undef_symbols) return 0; /* no symbol file specified */ if (nb_undef_symbols == -1) return 0; /* no symbol file specified */
add_extra_undef_symbols(); add_extra_undef_symbols();
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include "winbase.h"
#include "build.h" #include "build.h"
int current_line = 0; int current_line = 0;
...@@ -45,9 +46,6 @@ static const char * const TypeNames[TYPE_NBTYPES] = ...@@ -45,9 +46,6 @@ static const char * const TypeNames[TYPE_NBTYPES] =
"forward" /* TYPE_FORWARD */ "forward" /* TYPE_FORWARD */
}; };
/* callback function used for stub functions */
#define STUB_CALLBACK \
((SpecType == SPEC_WIN16) ? "RELAY_Unimplemented16": "RELAY_Unimplemented32")
static int IsNumberString(char *s) static int IsNumberString(char *s)
{ {
...@@ -280,7 +278,7 @@ static void ParseEquate( ORDDEF *odp ) ...@@ -280,7 +278,7 @@ static void ParseEquate( ORDDEF *odp )
static void ParseStub( ORDDEF *odp ) static void ParseStub( ORDDEF *odp )
{ {
odp->u.func.arg_types[0] = '\0'; odp->u.func.arg_types[0] = '\0';
strcpy( odp->u.func.link_name, STUB_CALLBACK ); odp->u.func.link_name[0] = '\0';
} }
...@@ -438,6 +436,40 @@ static void ParseOrdinal(int ordinal) ...@@ -438,6 +436,40 @@ static void ParseOrdinal(int ordinal)
} }
static int name_compare( const void *name1, const void *name2 )
{
ORDDEF *odp1 = *(ORDDEF **)name1;
ORDDEF *odp2 = *(ORDDEF **)name2;
return strcmp( odp1->name, odp2->name );
}
/*******************************************************************
* sort_names
*
* Sort the name array and catch duplicates.
*/
static void sort_names(void)
{
int i;
if (!nb_names) return;
/* sort the list of names */
qsort( Names, nb_names, sizeof(Names[0]), name_compare );
/* check for duplicate names */
for (i = 0; i < nb_names - 1; i++)
{
if (!strcmp( Names[i]->name, Names[i+1]->name ))
{
current_line = max( Names[i]->lineno, Names[i+1]->lineno );
fatal_error( "'%s' redefined (previous definition at line %d)\n",
Names[i]->name, min( Names[i]->lineno, Names[i+1]->lineno ) );
}
}
}
/******************************************************************* /*******************************************************************
* ParseTopLevel * ParseTopLevel
* *
...@@ -543,5 +575,6 @@ SPEC_TYPE ParseTopLevel( FILE *file ) ...@@ -543,5 +575,6 @@ SPEC_TYPE ParseTopLevel( FILE *file )
fatal_error( "'owner' not specified for Win16 dll\n" ); fatal_error( "'owner' not specified for Win16 dll\n" );
current_line = 0; /* no longer parsing the input file */ current_line = 0; /* no longer parsing the input file */
sort_names();
return SpecType; return SpecType;
} }
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include "wine/exception.h"
#include "builtin16.h" #include "builtin16.h"
#include "module.h" #include "module.h"
#include "neexe.h" #include "neexe.h"
...@@ -494,6 +495,58 @@ static int Spec16TypeCompare( const void *e1, const void *e2 ) ...@@ -494,6 +495,58 @@ static int Spec16TypeCompare( const void *e1, const void *e2 )
/******************************************************************* /*******************************************************************
* output_stub_funcs
*
* Output the functions for stub entry points
*/
static void output_stub_funcs( FILE *outfile )
{
int i;
char *p;
for (i = 0; i <= Limit; i++)
{
ORDDEF *odp = Ordinals[i];
if (!odp || odp->type != TYPE_STUB) continue;
fprintf( outfile, "#ifdef __GNUC__\n" );
fprintf( outfile, "static void __wine_unimplemented( const char *func ) __attribute__((noreturn));\n" );
fprintf( outfile, "#endif\n" );
fprintf( outfile, "static void __wine_unimplemented( const char *func )\n{\n" );
fprintf( outfile, " struct exc_record {\n" );
fprintf( outfile, " unsigned int code, flags;\n" );
fprintf( outfile, " void *rec, *addr;\n" );
fprintf( outfile, " unsigned int params;\n" );
fprintf( outfile, " const void *info[15];\n" );
fprintf( outfile, " } rec;\n" );
fprintf( outfile, " extern void RtlRaiseException( struct exc_record * );\n\n" );
fprintf( outfile, " rec.code = 0x%08x;\n", EXCEPTION_WINE_STUB );
fprintf( outfile, " rec.flags = %d;\n", EH_NONCONTINUABLE );
fprintf( outfile, " rec.rec = 0;\n" );
fprintf( outfile, " rec.params = 2;\n" );
fprintf( outfile, " rec.info[0] = dllname;\n" );
fprintf( outfile, " rec.info[1] = func;\n" );
fprintf( outfile, "#ifdef __GNUC__\n" );
fprintf( outfile, " rec.addr = __builtin_return_address(1);\n" );
fprintf( outfile, "#else\n" );
fprintf( outfile, " rec.addr = 0;\n" );
fprintf( outfile, "#endif\n" );
fprintf( outfile, " for (;;) RtlRaiseException( &rec );\n}\n\n" );
break;
}
for (i = 0; i <= Limit; i++)
{
ORDDEF *odp = Ordinals[i];
if (!odp || odp->type != TYPE_STUB) continue;
strcpy( odp->u.func.link_name, "__stub_" );
strcat( odp->u.func.link_name, odp->name );
for (p = odp->u.func.link_name; *p; p++) if (!isalnum(*p)) *p = '_';
fprintf( outfile, "static void %s(void) { __wine_unimplemented(\"%s\"); }\n",
odp->u.func.link_name, odp->name );
}
}
/*******************************************************************
* BuildSpec16File * BuildSpec16File
* *
* Build a Win16 assembly file from a spec file. * Build a Win16 assembly file from a spec file.
...@@ -519,6 +572,9 @@ void BuildSpec16File( FILE *outfile ) ...@@ -519,6 +572,9 @@ void BuildSpec16File( FILE *outfile )
data_offset = 16; data_offset = 16;
strupper( DLLName ); strupper( DLLName );
fprintf( outfile, "static const char dllname[] = \"%s\";\n\n", DLLName );
output_stub_funcs( outfile );
/* Build sorted list of all argument types, without duplicates */ /* Build sorted list of all argument types, without duplicates */
typelist = (ORDDEF **)calloc( Limit+1, sizeof(ORDDEF *) ); typelist = (ORDDEF **)calloc( Limit+1, sizeof(ORDDEF *) );
...@@ -615,7 +671,6 @@ void BuildSpec16File( FILE *outfile ) ...@@ -615,7 +671,6 @@ void BuildSpec16File( FILE *outfile )
case 's': /* s_word */ case 's': /* s_word */
argsize += 2; argsize += 2;
break; break;
case 'l': /* long or segmented pointer */ case 'l': /* long or segmented pointer */
case 'T': /* segmented pointer to null-terminated string */ case 'T': /* segmented pointer to null-terminated string */
case 'p': /* linear pointer */ case 'p': /* linear pointer */
...@@ -679,24 +734,23 @@ void BuildSpec16File( FILE *outfile ) ...@@ -679,24 +734,23 @@ void BuildSpec16File( FILE *outfile )
fprintf( outfile, " /* %s.%d */ ", DLLName, i ); fprintf( outfile, " /* %s.%d */ ", DLLName, i );
fprintf( outfile, "{ 0x5566, 0x68, %s, 0xe866, %d /* %s_%s_%s */ },\n", fprintf( outfile, "{ 0x5566, 0x68, %s, 0xe866, %d /* %s_%s_%s */ },\n",
odp->u.func.link_name, odp->u.func.link_name,
(type-typelist)*sizeof(CALLFROM16) - (type-typelist)*sizeof(CALLFROM16) -
(code_offset + sizeof(ENTRYPOINT16)), (code_offset + sizeof(ENTRYPOINT16)),
(odp->type == TYPE_CDECL) ? "c" : "p", (odp->type == TYPE_CDECL) ? "c" : "p",
(odp->type == TYPE_REGISTER) ? "regs" : (odp->type == TYPE_REGISTER) ? "regs" :
(odp->type == TYPE_INTERRUPT) ? "intr" : (odp->type == TYPE_INTERRUPT) ? "intr" :
(odp->type == TYPE_PASCAL_16) ? "word" : "long", (odp->type == TYPE_PASCAL_16) ? "word" : "long",
odp->u.func.arg_types ); odp->u.func.arg_types );
odp->offset = code_offset; odp->offset = code_offset;
code_offset += sizeof(ENTRYPOINT16); code_offset += sizeof(ENTRYPOINT16);
break; break;
default: default:
fprintf(stderr,"build: function type %d not available for Win16\n", fprintf(stderr,"build: function type %d not available for Win16\n",
odp->type); odp->type);
exit(1); exit(1);
} }
} }
fprintf( outfile, " }\n};\n" ); fprintf( outfile, " }\n};\n" );
......
...@@ -17,13 +17,6 @@ ...@@ -17,13 +17,6 @@
#include "build.h" #include "build.h"
static int name_compare( const void *name1, const void *name2 )
{
ORDDEF *odp1 = *(ORDDEF **)name1;
ORDDEF *odp2 = *(ORDDEF **)name2;
return strcmp( odp1->name, odp2->name );
}
static int string_compare( const void *ptr1, const void *ptr2 ) static int string_compare( const void *ptr1, const void *ptr2 )
{ {
const char * const *str1 = ptr1; const char * const *str1 = ptr1;
...@@ -42,20 +35,6 @@ static void AssignOrdinals(void) ...@@ -42,20 +35,6 @@ static void AssignOrdinals(void)
if ( !nb_names ) return; if ( !nb_names ) return;
/* sort the list of names */
qsort( Names, nb_names, sizeof(Names[0]), name_compare );
/* check for duplicate names */
for (i = 0; i < nb_names - 1; i++)
{
if (!strcmp( Names[i]->name, Names[i+1]->name ))
{
current_line = max( Names[i]->lineno, Names[i+1]->lineno );
fatal_error( "'%s' redefined (previous definition at line %d)\n",
Names[i]->name, min( Names[i]->lineno, Names[i+1]->lineno ) );
}
}
/* start assigning from Base, or from 1 if no ordinal defined yet */ /* start assigning from Base, or from 1 if no ordinal defined yet */
if (Base == MAX_ORDINALS) Base = 1; if (Base == MAX_ORDINALS) Base = 1;
for (i = 0, ordinal = Base; i < nb_names; i++) for (i = 0, ordinal = Base; i < nb_names; i++)
...@@ -318,6 +297,58 @@ static void output_exports( FILE *outfile, int nr_exports, int fwd_size ) ...@@ -318,6 +297,58 @@ static void output_exports( FILE *outfile, int nr_exports, int fwd_size )
/******************************************************************* /*******************************************************************
* output_stub_funcs
*
* Output the functions for stub entry points
*/
static void output_stub_funcs( FILE *outfile )
{
int i;
ORDDEF *odp;
for (i = 0, odp = EntryPoints; i < nb_entry_points; i++, odp++)
{
if (odp->type != TYPE_STUB) continue;
fprintf( outfile, "#ifdef __GNUC__\n" );
fprintf( outfile, "static void __wine_unimplemented( const char *func ) __attribute__((noreturn));\n" );
fprintf( outfile, "#endif\n" );
fprintf( outfile, "static void __wine_unimplemented( const char *func )\n{\n" );
fprintf( outfile, " struct exc_record {\n" );
fprintf( outfile, " unsigned int code, flags;\n" );
fprintf( outfile, " void *rec, *addr;\n" );
fprintf( outfile, " unsigned int params;\n" );
fprintf( outfile, " const void *info[15];\n" );
fprintf( outfile, " } rec;\n" );
fprintf( outfile, " extern void RtlRaiseException( struct exc_record * );\n\n" );
fprintf( outfile, " rec.code = 0x%08x;\n", EXCEPTION_WINE_STUB );
fprintf( outfile, " rec.flags = %d;\n", EH_NONCONTINUABLE );
fprintf( outfile, " rec.rec = 0;\n" );
fprintf( outfile, " rec.params = 2;\n" );
fprintf( outfile, " rec.info[0] = dllname;\n" );
fprintf( outfile, " rec.info[1] = func;\n" );
fprintf( outfile, "#ifdef __GNUC__\n" );
fprintf( outfile, " rec.addr = __builtin_return_address(1);\n" );
fprintf( outfile, "#else\n" );
fprintf( outfile, " rec.addr = 0;\n" );
fprintf( outfile, "#endif\n" );
fprintf( outfile, " for (;;) RtlRaiseException( &rec );\n}\n\n" );
break;
}
for (i = 0, odp = EntryPoints; i < nb_entry_points; i++, odp++)
{
if (odp->type != TYPE_STUB) continue;
if (odp->name[0])
fprintf( outfile, "static void __stub_%s(void) { __wine_unimplemented(\"%s\"); }\n",
odp->name, odp->name );
else
fprintf( outfile, "static void __stub_%d(void) { __wine_unimplemented(\"%d\"); }\n",
odp->ordinal, odp->ordinal );
}
}
/*******************************************************************
* BuildSpec32File * BuildSpec32File
* *
* Build a Win32 C file from a spec file. * Build a Win32 C file from a spec file.
...@@ -356,36 +387,9 @@ void BuildSpec32File( FILE *outfile, int output_main ) ...@@ -356,36 +387,9 @@ void BuildSpec32File( FILE *outfile, int output_main )
fprintf( outfile, "static const char dllname[] = \"%s\";\n\n", DLLName ); fprintf( outfile, "static const char dllname[] = \"%s\";\n\n", DLLName );
/* Output the stub function if necessary */ /* Output the stub functions */
for (i = 0, odp = EntryPoints; i < nb_entry_points; i++, odp++) output_stub_funcs( outfile );
{
if (odp->type != TYPE_STUB) continue;
fprintf( outfile, "#ifdef __GNUC__\n" );
fprintf( outfile, "static void __wine_unimplemented( const char *func ) __attribute__((noreturn));\n" );
fprintf( outfile, "#endif\n" );
fprintf( outfile, "static void __wine_unimplemented( const char *func )\n{\n" );
fprintf( outfile, " struct exc_record {\n" );
fprintf( outfile, " unsigned int code, flags;\n" );
fprintf( outfile, " void *rec, *addr;\n" );
fprintf( outfile, " unsigned int params;\n" );
fprintf( outfile, " const void *info[15];\n" );
fprintf( outfile, " } rec;\n" );
fprintf( outfile, " extern void RtlRaiseException( struct exc_record * );\n\n" );
fprintf( outfile, " rec.code = 0x%08x;\n", EXCEPTION_WINE_STUB );
fprintf( outfile, " rec.flags = %d;\n", EH_NONCONTINUABLE );
fprintf( outfile, " rec.rec = 0;\n" );
fprintf( outfile, " rec.params = 2;\n" );
fprintf( outfile, " rec.info[0] = dllname;\n" );
fprintf( outfile, " rec.info[1] = func;\n" );
fprintf( outfile, "#ifdef __GNUC__\n" );
fprintf( outfile, " rec.addr = __builtin_return_address(1);\n" );
fprintf( outfile, "#else\n" );
fprintf( outfile, " rec.addr = 0;\n" );
fprintf( outfile, "#endif\n" );
fprintf( outfile, " for (;;) RtlRaiseException( &rec );\n}\n\n" );
break;
}
/* Output the DLL functions prototypes */ /* Output the DLL functions prototypes */
...@@ -410,14 +414,6 @@ void BuildSpec32File( FILE *outfile, int output_main ) ...@@ -410,14 +414,6 @@ void BuildSpec32File( FILE *outfile, int output_main )
have_regs = TRUE; have_regs = TRUE;
break; break;
case TYPE_STUB: case TYPE_STUB:
if (odp->name[0])
fprintf( outfile,
"static void __stub_%s() { __wine_unimplemented(\"%s\"); }\n",
odp->name, odp->name );
else
fprintf( outfile,
"static void __stub_%d() { __wine_unimplemented(\"%d\"); }\n",
odp->ordinal, odp->ordinal );
break; break;
default: default:
fprintf(stderr,"build: function type %d not available for Win32\n", fprintf(stderr,"build: function type %d not available for Win32\n",
......
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