unittest
{
	assert( [ "1 1 2 2" ].solve == 3 );
	assert( [ "2 1 3 4" ].solve == 65 );
	assert( [ "31 41 59 265" ].solve == 387222020 );
}



import std.algorithm;
import std.conv;
import std.format;
import std.range;
import std.stdio;

void main()
{
	stdin.byLineCopy.solve.writeln;
}

auto solve( Range )( Range input )
if( isInputRange!Range && is( ElementType!Range == string ) )
{
	auto a = 0L;
	auto b = 0L;
	auto c = 0L;
	auto d = 0L;
	input.front.formattedRead!"%s %s %s %s"( a, b, c, d );
	
	// dp[ 縦幅 ][ 横幅 ][ 一番上の行の黒マスの数(0,1,2以上) ] = パターン数
	const M = 998244353L;
	auto dp = new ModWorld!M[][][]( 1 + c, 1 + d, 3 );
	dp[ a ][ b ][ 0 ] = 1;
	foreach( h; a + 1 .. dp.length.signed )
	{
		dp[ h ][ b ][ 1 ] = dp[ h - 1 ][ b ].sum * b;
	}
	foreach( w; b + 1 .. dp[ 0 ].length.signed )
	{
		dp[ a ][ w ][ 0 ] = dp[ a ][ w - 1 ][ 0 ] * ( a - 1 );
		dp[ a ][ w ][ 1 ] = dp[ a ][ w - 1 ][ 1 ] * ( a - 1 ) + dp[ a ][ w - 1 ][ 0 ];
		dp[ a ][ w ][ 2 ] = dp[ a ][ w - 1 ][ 2 ] *   a       + dp[ a ][ w - 1 ][ 1 ];
	}
	foreach( h; a + 1 .. dp.length.signed )
	{
		foreach( w; b + 1 .. dp[ h ].length.signed )
		{
			dp[ h ][ w ][ 0 ] = dp[ h ][ w - 1 ][ 0 ] * ( h - 1 );
			dp[ h ][ w ][ 1 ] = dp[ h - 1 ][ w ].sum * w;
			dp[ h ][ w ][ 2 ] = dp[ h ][ w - 1 ][ 2 ] * h + dp[ h ][ w - 1 ][ 1 ];
			
		}
	}
	return dp[ $ - 1 ][ $ - 1 ].sum;
}

// モジュラ整数型（n mod M）
struct ModWorld( long M )
{
	long n;
	
	void opUnary( string OP )() if( OP == "++" ){ this.n = ( this.n + 1 ) % M; }
	void opUnary( string OP )() if( OP == "--" ){ this.n = ( this.n + M - 1 ) % M; }
	void opOpAssign( string OP )( ModWorld r ) if( OP == "+"  ){ this.n = ( this.n + r.n ) % M; }
	void opOpAssign( string OP )( ModWorld r ) if( OP == "-"  ){ this.n = ( this.n + M - r.n ) % M; }
	void opOpAssign( string OP )( ModWorld r ) if( OP == "*"  ){ this.n = ( this.n * r.n ) % M; }
	void opOpAssign( string OP )( ModWorld r ) if( OP == "/"  ){ this *= r ^^ ModWorld( M - 2 ); }
	void opOpAssign( string OP )( ModWorld r ) if( OP == "^^" )
	{
		auto a = this.n;
		this.n = 1L;
		for( auto i = r.n; 0 < i; i >>= 1 )
		{
			if( i & 1 ) this.n = ( this.n * a ) % M;
			a = ( a * a ) % M;
		}
	}
	auto opBinary( string OP )( ModWorld r ){ auto l = this; l.opOpAssign!OP( r ); return l; }
	
	void opAssign( long r ){ this.n = r; }
	void opOpAssign( string OP )( long r ){ this.opOpAssign!OP( ModWorld( r ) ); }
	auto opBinary( string OP )( long r ){ return this.opBinary!OP( ModWorld( r ) ); }
	auto opBinaryRight( string OP )( long l ){ return ModWorld( l ).opBinary!OP( this ); }
	auto opEquals( long r ){ return this.n == r; }
	
	auto toString(){ return this.n.to!string; }
}
