mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2026-04-21 05:02:03 +00:00
Tamper generic explosion and code style
This commit is contained in:
parent
b9da6a15d7
commit
529d6f44bb
30 changed files with 180 additions and 98 deletions
|
|
@ -68,8 +68,8 @@ namespace Ryujinx.HLE.Generators
|
||||||
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
{
|
{
|
||||||
var predicate = (SyntaxNode node, CancellationToken _) => node is MethodDeclarationSyntax;
|
Func<SyntaxNode, CancellationToken, bool> predicate = (node, _) => node is MethodDeclarationSyntax;
|
||||||
var transform = (GeneratorAttributeSyntaxContext ctx, CancellationToken _) =>
|
Func<GeneratorAttributeSyntaxContext, CancellationToken, CommandData> transform = (ctx, _) =>
|
||||||
{
|
{
|
||||||
var target = (IMethodSymbol)ctx.TargetSymbol;
|
var target = (IMethodSymbol)ctx.TargetSymbol;
|
||||||
return new CommandData
|
return new CommandData
|
||||||
|
|
@ -80,27 +80,28 @@ namespace Ryujinx.HLE.Generators
|
||||||
CommandIds = ctx.Attributes.Select(attr => (int)attr.ConstructorArguments[0].Value!).ToImmutableArray(),
|
CommandIds = ctx.Attributes.Select(attr => (int)attr.ConstructorArguments[0].Value!).ToImmutableArray(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
var cmifCommands =
|
IncrementalValuesProvider<CommandData> cmifCommands =
|
||||||
context.SyntaxProvider.ForAttributeWithMetadataName("Ryujinx.HLE.HOS.Services.CommandCmifAttribute",
|
context.SyntaxProvider.ForAttributeWithMetadataName("Ryujinx.HLE.HOS.Services.CommandCmifAttribute",
|
||||||
predicate,
|
predicate,
|
||||||
transform
|
transform
|
||||||
);
|
);
|
||||||
var tipcCommands =
|
IncrementalValuesProvider<CommandData> tipcCommands =
|
||||||
context.SyntaxProvider.ForAttributeWithMetadataName("Ryujinx.HLE.HOS.Services.CommandTipcAttribute",
|
context.SyntaxProvider.ForAttributeWithMetadataName("Ryujinx.HLE.HOS.Services.CommandTipcAttribute",
|
||||||
predicate,
|
predicate,
|
||||||
transform
|
transform
|
||||||
);
|
);
|
||||||
|
|
||||||
var allCommands = cmifCommands.Collect().Combine(tipcCommands.Collect());
|
IncrementalValueProvider<(ImmutableArray<CommandData> Left, ImmutableArray<CommandData> Right)> allCommands =
|
||||||
|
cmifCommands.Collect().Combine(tipcCommands.Collect());
|
||||||
|
|
||||||
var types = allCommands.SelectMany((commands, _) =>
|
IncrementalValuesProvider<ServiceData> types = allCommands.SelectMany((commands, _) =>
|
||||||
{
|
{
|
||||||
var cmif = commands.Left.ToLookup(c => (c.Namespace, c.TypeName));
|
ILookup<(string Namespace, string TypeName), CommandData> cmif = commands.Left.ToLookup(c => (c.Namespace, c.TypeName));
|
||||||
var tipc = commands.Right.ToLookup(c => (c.Namespace, c.TypeName));
|
ILookup<(string Namespace, string TypeName), CommandData> tipc = commands.Right.ToLookup(c => (c.Namespace, c.TypeName));
|
||||||
|
|
||||||
var builder = ImmutableArray.CreateBuilder<ServiceData>();
|
ImmutableArray<ServiceData>.Builder builder = ImmutableArray.CreateBuilder<ServiceData>();
|
||||||
|
|
||||||
foreach (var type in cmif.Select(c => c.Key).Union(tipc.Select(t => t.Key)))
|
foreach ((string Namespace, string TypeName) type in cmif.Select(c => c.Key).Union(tipc.Select(t => t.Key)))
|
||||||
{
|
{
|
||||||
builder.Add(new ServiceData
|
builder.Add(new ServiceData
|
||||||
{
|
{
|
||||||
|
|
@ -143,7 +144,7 @@ namespace Ryujinx.HLE.Generators
|
||||||
{
|
{
|
||||||
generator.EnterScope($"protected override RC Invoke{commandType}Method(int id, ServiceCtx context)");
|
generator.EnterScope($"protected override RC Invoke{commandType}Method(int id, ServiceCtx context)");
|
||||||
generator.EnterScope("switch (id)");
|
generator.EnterScope("switch (id)");
|
||||||
foreach (var command in commands)
|
foreach (CommandData command in commands)
|
||||||
{
|
{
|
||||||
generator.AppendLine($"case {string.Join(" or ", command.CommandIds)}:");
|
generator.AppendLine($"case {string.Join(" or ", command.CommandIds)}:");
|
||||||
generator.IncreaseIndentation();
|
generator.IncreaseIndentation();
|
||||||
|
|
@ -157,7 +158,7 @@ namespace Ryujinx.HLE.Generators
|
||||||
|
|
||||||
generator.EnterScope($"public override int {commandType}CommandIdByMethodName(string name)");
|
generator.EnterScope($"public override int {commandType}CommandIdByMethodName(string name)");
|
||||||
generator.EnterScope("return name switch");
|
generator.EnterScope("return name switch");
|
||||||
foreach (var command in commands)
|
foreach (CommandData command in commands)
|
||||||
{
|
{
|
||||||
// just return the first command with this name
|
// just return the first command with this name
|
||||||
generator.AppendLine($"\"{command.MethodName}\" => {command.CommandIds[0]},");
|
generator.AppendLine($"\"{command.MethodName}\" => {command.CommandIds[0]},");
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,12 @@ namespace Ryujinx.HLE.Generators
|
||||||
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
{
|
{
|
||||||
var pipeline = context.SyntaxProvider.ForAttributeWithMetadataName("Ryujinx.HLE.HOS.Services.ServiceAttribute",
|
IncrementalValuesProvider<ServiceData> pipeline = context.SyntaxProvider.ForAttributeWithMetadataName("Ryujinx.HLE.HOS.Services.ServiceAttribute",
|
||||||
predicate: (node, _) => node is ClassDeclarationSyntax decl && !decl.Modifiers.Any(SyntaxKind.AbstractKeyword) && !decl.Modifiers.Any(SyntaxKind.PrivateKeyword),
|
predicate: (node, _) => node is ClassDeclarationSyntax decl && !decl.Modifiers.Any(SyntaxKind.AbstractKeyword) && !decl.Modifiers.Any(SyntaxKind.PrivateKeyword),
|
||||||
transform: (ctx, _) =>
|
transform: (ctx, _) =>
|
||||||
{
|
{
|
||||||
var target = (INamedTypeSymbol)ctx.TargetSymbol;
|
var target = (INamedTypeSymbol)ctx.TargetSymbol;
|
||||||
var instances = ctx.Attributes.Select(attr =>
|
IEnumerable<(string, string param)> instances = ctx.Attributes.Select(attr =>
|
||||||
{
|
{
|
||||||
string param = attr.ConstructorArguments is [_, { IsNull: false } arg] ? arg.ToCSharpString() : null;
|
string param = attr.ConstructorArguments is [_, { IsNull: false } arg] ? arg.ToCSharpString() : null;
|
||||||
return ((string)attr.ConstructorArguments[0].Value, param);
|
return ((string)attr.ConstructorArguments[0].Value, param);
|
||||||
|
|
@ -60,9 +60,9 @@ namespace Ryujinx.HLE.Generators
|
||||||
|
|
||||||
generator.EnterScope("return name switch");
|
generator.EnterScope("return name switch");
|
||||||
|
|
||||||
foreach (var serviceImpl in data)
|
foreach (ServiceData serviceImpl in data)
|
||||||
{
|
{
|
||||||
foreach (var instance in serviceImpl.Instances)
|
foreach ((string ServiceName, string ParameterValue) instance in serviceImpl.Instances)
|
||||||
{
|
{
|
||||||
if (instance.ParameterValue == null)
|
if (instance.ParameterValue == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ namespace Ryujinx.HLE.Exceptions
|
||||||
{
|
{
|
||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
|
|
||||||
var commandId = Request.Type > IpcMessageType.TipcCloseSession
|
int commandId = Request.Type > IpcMessageType.TipcCloseSession
|
||||||
? Service.TipcCommandIdByMethodName(MethodName)
|
? Service.TipcCommandIdByMethodName(MethodName)
|
||||||
: Service.CmifCommandIdByMethodName(MethodName);
|
: Service.CmifCommandIdByMethodName(MethodName);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
|
||||||
throw new TamperCompilationException($"Invalid right-hand side switch {rightHandSideIsImmediate} in Atmosphere cheat");
|
throw new TamperCompilationException($"Invalid right-hand side switch {rightHandSideIsImmediate} in Atmosphere cheat");
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmitCore<TOp>(IOperand rhs = null) where TOp : IOperation
|
void EmitCore<TOp>(IOperand rhs = null) where TOp : IOperationFactory
|
||||||
{
|
{
|
||||||
InstructionHelper.Emit<TOp>(operationWidth, context, destinationRegister, leftHandSideRegister, rhs);
|
InstructionHelper.Emit<TOp>(operationWidth, context, destinationRegister, leftHandSideRegister, rhs);
|
||||||
}
|
}
|
||||||
|
|
@ -77,34 +77,34 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
|
||||||
switch (operation)
|
switch (operation)
|
||||||
{
|
{
|
||||||
case Add:
|
case Add:
|
||||||
EmitCore<OpAdd<byte>>(rightHandSideOperand);
|
EmitCore<OpAddFactory>(rightHandSideOperand);
|
||||||
break;
|
break;
|
||||||
case Sub:
|
case Sub:
|
||||||
EmitCore<OpSub<byte>>(rightHandSideOperand);
|
EmitCore<OpSubFactory>(rightHandSideOperand);
|
||||||
break;
|
break;
|
||||||
case Mul:
|
case Mul:
|
||||||
EmitCore<OpMul<byte>>(rightHandSideOperand);
|
EmitCore<OpMulFactory>(rightHandSideOperand);
|
||||||
break;
|
break;
|
||||||
case Lsh:
|
case Lsh:
|
||||||
EmitCore<OpLsh<byte>>(rightHandSideOperand);
|
EmitCore<OpLshFactory>(rightHandSideOperand);
|
||||||
break;
|
break;
|
||||||
case Rsh:
|
case Rsh:
|
||||||
EmitCore<OpRsh<byte>>(rightHandSideOperand);
|
EmitCore<OpRshFactory>(rightHandSideOperand);
|
||||||
break;
|
break;
|
||||||
case And:
|
case And:
|
||||||
EmitCore<OpAnd<byte>>(rightHandSideOperand);
|
EmitCore<OpAndFactory>(rightHandSideOperand);
|
||||||
break;
|
break;
|
||||||
case Or:
|
case Or:
|
||||||
EmitCore<OpOr<byte>>(rightHandSideOperand);
|
EmitCore<OpOrFactory>(rightHandSideOperand);
|
||||||
break;
|
break;
|
||||||
case Not:
|
case Not:
|
||||||
EmitCore<OpNot<byte>>();
|
EmitCore<OpNotFactory>();
|
||||||
break;
|
break;
|
||||||
case Xor:
|
case Xor:
|
||||||
EmitCore<OpXor<byte>>(rightHandSideOperand);
|
EmitCore<OpXorFactory>(rightHandSideOperand);
|
||||||
break;
|
break;
|
||||||
case Mov:
|
case Mov:
|
||||||
EmitCore<OpMov<byte>>();
|
EmitCore<OpMovFactory>();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new TamperCompilationException($"Invalid arithmetic operation {operation} in Atmosphere cheat");
|
throw new TamperCompilationException($"Invalid arithmetic operation {operation} in Atmosphere cheat");
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
|
||||||
ulong immediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, ValueImmediateSize);
|
ulong immediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, ValueImmediateSize);
|
||||||
Value<ulong> rightHandSideValue = new(immediate);
|
Value<ulong> rightHandSideValue = new(immediate);
|
||||||
|
|
||||||
void EmitCore<TOp>() where TOp : IOperation
|
void EmitCore<TOp>() where TOp : IOperationFactory
|
||||||
{
|
{
|
||||||
InstructionHelper.Emit<TOp>(operationWidth, context, register, register, rightHandSideValue);
|
InstructionHelper.Emit<TOp>(operationWidth, context, register, register, rightHandSideValue);
|
||||||
}
|
}
|
||||||
|
|
@ -45,19 +45,19 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
|
||||||
switch (operation)
|
switch (operation)
|
||||||
{
|
{
|
||||||
case Add:
|
case Add:
|
||||||
EmitCore<OpAdd<byte>>();
|
EmitCore<OpAddFactory>();
|
||||||
break;
|
break;
|
||||||
case Sub:
|
case Sub:
|
||||||
EmitCore<OpAdd<byte>>();
|
EmitCore<OpAddFactory>();
|
||||||
break;
|
break;
|
||||||
case Mul:
|
case Mul:
|
||||||
EmitCore<OpMul<byte>>();
|
EmitCore<OpMulFactory>();
|
||||||
break;
|
break;
|
||||||
case Lsh:
|
case Lsh:
|
||||||
EmitCore<OpLsh<byte>>();
|
EmitCore<OpLshFactory>();
|
||||||
break;
|
break;
|
||||||
case Rsh:
|
case Rsh:
|
||||||
EmitCore<OpRsh<byte>>();
|
EmitCore<OpRshFactory>();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new TamperCompilationException($"Invalid arithmetic operation {operation} in Atmosphere cheat");
|
throw new TamperCompilationException($"Invalid arithmetic operation {operation} in Atmosphere cheat");
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
|
||||||
throw new TamperCompilationException($"Invalid source mode {useDestinationAsSourceIndex} in Atmosphere cheat");
|
throw new TamperCompilationException($"Invalid source mode {useDestinationAsSourceIndex} in Atmosphere cheat");
|
||||||
}
|
}
|
||||||
|
|
||||||
InstructionHelper.Emit<OpMov<byte>>(operationWidth, context, destinationRegister, sourceMemory, null);
|
InstructionHelper.Emit<OpMovFactory>(operationWidth, context, destinationRegister, sourceMemory, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
|
||||||
ulong valueImmediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, valueImmediateSize);
|
ulong valueImmediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, valueImmediateSize);
|
||||||
Value<ulong> storeValue = new(valueImmediate);
|
Value<ulong> storeValue = new(valueImmediate);
|
||||||
|
|
||||||
InstructionHelper.Emit<OpMov<byte>>(operationWidth, context, dstMem, storeValue, null);
|
InstructionHelper.Emit<OpMovFactory>(operationWidth, context, dstMem, storeValue, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
|
||||||
throw new TamperCompilationException($"Invalid offset mode {useOffsetRegister} in Atmosphere cheat");
|
throw new TamperCompilationException($"Invalid offset mode {useOffsetRegister} in Atmosphere cheat");
|
||||||
}
|
}
|
||||||
|
|
||||||
InstructionHelper.Emit<OpMov<byte>>(operationWidth, context, destinationMemory, storeValue, null);
|
InstructionHelper.Emit<OpMovFactory>(operationWidth, context, destinationMemory, storeValue, null);
|
||||||
|
|
||||||
switch (incrementAddressRegister)
|
switch (incrementAddressRegister)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
|
||||||
throw new TamperCompilationException($"Invalid offset type {offsetType} in Atmosphere cheat");
|
throw new TamperCompilationException($"Invalid offset type {offsetType} in Atmosphere cheat");
|
||||||
}
|
}
|
||||||
|
|
||||||
InstructionHelper.Emit<OpMov<byte>>(operationWidth, context, destinationMemory, sourceRegister, null);
|
InstructionHelper.Emit<OpMovFactory>(operationWidth, context, destinationMemory, sourceRegister, null);
|
||||||
|
|
||||||
switch (incrementAddressRegister)
|
switch (incrementAddressRegister)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||||
{
|
{
|
||||||
|
sealed class CondEQFactory : IConditionFactory
|
||||||
|
{
|
||||||
|
private CondEQFactory() { }
|
||||||
|
|
||||||
|
public static ICondition CreateFor<T>(IOperand lhs, IOperand rhs) where T : unmanaged, INumber<T>
|
||||||
|
=> new CondEQ<T>(lhs, rhs);
|
||||||
|
}
|
||||||
class CondEQ<T> : ICondition where T : unmanaged, INumber<T>
|
class CondEQ<T> : ICondition where T : unmanaged, INumber<T>
|
||||||
{
|
{
|
||||||
private readonly IOperand _lhs;
|
private readonly IOperand _lhs;
|
||||||
|
|
@ -18,8 +25,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||||
{
|
{
|
||||||
return _lhs.Get<T>() == _rhs.Get<T>();
|
return _lhs.Get<T>() == _rhs.Get<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ICondition CreateFor<T1>(IOperand lhs, IOperand rhs) where T1 : INumber<T1>
|
|
||||||
=> new CondEQ<T>(lhs, rhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||||
{
|
{
|
||||||
|
sealed class CondGEFactory : IConditionFactory
|
||||||
|
{
|
||||||
|
private CondGEFactory() { }
|
||||||
|
|
||||||
|
public static ICondition CreateFor<T>(IOperand lhs, IOperand rhs) where T : unmanaged, INumber<T>
|
||||||
|
=> new CondGE<T>(lhs, rhs);
|
||||||
|
}
|
||||||
class CondGE<T> : ICondition where T : unmanaged, INumber<T>
|
class CondGE<T> : ICondition where T : unmanaged, INumber<T>
|
||||||
{
|
{
|
||||||
private readonly IOperand _lhs;
|
private readonly IOperand _lhs;
|
||||||
|
|
@ -18,8 +25,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||||
{
|
{
|
||||||
return _lhs.Get<T>() >= _rhs.Get<T>();
|
return _lhs.Get<T>() >= _rhs.Get<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ICondition CreateFor<T1>(IOperand lhs, IOperand rhs) where T1 : INumber<T1>
|
|
||||||
=> new CondGE<T>(lhs, rhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||||
{
|
{
|
||||||
|
sealed class CondGTFactory : IConditionFactory
|
||||||
|
{
|
||||||
|
private CondGTFactory() { }
|
||||||
|
|
||||||
|
public static ICondition CreateFor<T>(IOperand lhs, IOperand rhs) where T : unmanaged, INumber<T>
|
||||||
|
=> new CondGT<T>(lhs, rhs);
|
||||||
|
}
|
||||||
class CondGT<T> : ICondition where T : unmanaged, INumber<T>
|
class CondGT<T> : ICondition where T : unmanaged, INumber<T>
|
||||||
{
|
{
|
||||||
private readonly IOperand _lhs;
|
private readonly IOperand _lhs;
|
||||||
|
|
@ -18,8 +25,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||||
{
|
{
|
||||||
return _lhs.Get<T>() > _rhs.Get<T>();
|
return _lhs.Get<T>() > _rhs.Get<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ICondition CreateFor<T1>(IOperand lhs, IOperand rhs) where T1 : INumber<T1>
|
|
||||||
=> new CondGT<T>(lhs, rhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||||
{
|
{
|
||||||
|
sealed class CondLEFactory : IConditionFactory
|
||||||
|
{
|
||||||
|
private CondLEFactory() { }
|
||||||
|
|
||||||
|
public static ICondition CreateFor<T>(IOperand lhs, IOperand rhs) where T : unmanaged, INumber<T>
|
||||||
|
=> new CondLE<T>(lhs, rhs);
|
||||||
|
}
|
||||||
class CondLE<T> : ICondition where T : unmanaged, INumber<T>
|
class CondLE<T> : ICondition where T : unmanaged, INumber<T>
|
||||||
{
|
{
|
||||||
private readonly IOperand _lhs;
|
private readonly IOperand _lhs;
|
||||||
|
|
@ -18,8 +25,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||||
{
|
{
|
||||||
return _lhs.Get<T>() <= _rhs.Get<T>();
|
return _lhs.Get<T>() <= _rhs.Get<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ICondition CreateFor<T1>(IOperand lhs, IOperand rhs) where T1 : INumber<T1>
|
|
||||||
=> new CondLE<T>(lhs, rhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||||
{
|
{
|
||||||
|
sealed class CondLTFactory : IConditionFactory
|
||||||
|
{
|
||||||
|
private CondLTFactory() { }
|
||||||
|
|
||||||
|
public static ICondition CreateFor<T>(IOperand lhs, IOperand rhs) where T : unmanaged, INumber<T>
|
||||||
|
=> new CondLT<T>(lhs, rhs);
|
||||||
|
}
|
||||||
class CondLT<T> : ICondition where T : unmanaged, INumber<T>
|
class CondLT<T> : ICondition where T : unmanaged, INumber<T>
|
||||||
{
|
{
|
||||||
private readonly IOperand _lhs;
|
private readonly IOperand _lhs;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||||
{
|
{
|
||||||
|
sealed class CondNEFactory : IConditionFactory
|
||||||
|
{
|
||||||
|
private CondNEFactory() { }
|
||||||
|
|
||||||
|
public static ICondition CreateFor<T>(IOperand lhs, IOperand rhs) where T : unmanaged, INumber<T>
|
||||||
|
=> new CondNE<T>(lhs, rhs);
|
||||||
|
}
|
||||||
class CondNE<T> : ICondition where T : unmanaged, INumber<T>
|
class CondNE<T> : ICondition where T : unmanaged, INumber<T>
|
||||||
{
|
{
|
||||||
private readonly IOperand _lhs;
|
private readonly IOperand _lhs;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
using Ryujinx.HLE.HOS.Tamper.Operations;
|
|
||||||
using System;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||||
{
|
{
|
||||||
interface ICondition
|
interface ICondition
|
||||||
{
|
{
|
||||||
bool Evaluate();
|
bool Evaluate();
|
||||||
|
|
||||||
static virtual ICondition CreateFor<T>(IOperand lhs, IOperand rhs) where T : INumber<T> => throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
src/Ryujinx.HLE/HOS/Tamper/Conditions/IConditionFactory.cs
Normal file
10
src/Ryujinx.HLE/HOS/Tamper/Conditions/IConditionFactory.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
using Ryujinx.HLE.HOS.Tamper.Operations;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||||
|
{
|
||||||
|
interface IConditionFactory
|
||||||
|
{
|
||||||
|
static abstract ICondition CreateFor<T>(IOperand lhs, IOperand rhs) where T : unmanaged, INumber<T>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,14 +15,14 @@ namespace Ryujinx.HLE.HOS.Tamper
|
||||||
context.CurrentOperations.Add(operation);
|
context.CurrentOperations.Add(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Emit<TOp>(byte width, CompilationContext context, IOperand destination, IOperand lhs, IOperand rhs) where TOp : IOperation
|
public static void Emit<TOp>(byte width, CompilationContext context, IOperand destination, IOperand lhs, IOperand rhs) where TOp : IOperationFactory
|
||||||
{
|
{
|
||||||
Emit(Create<TOp>(width, destination, lhs, rhs), context);
|
Emit(Create<TOp>(width, destination, lhs, rhs), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ICondition CreateCondition(Comparison comparison, byte width, IOperand lhs, IOperand rhs)
|
public static ICondition CreateCondition(Comparison comparison, byte width, IOperand lhs, IOperand rhs)
|
||||||
{
|
{
|
||||||
ICondition CreateCore<TOp>() where TOp : ICondition
|
ICondition CreateCore<TOp>() where TOp : IConditionFactory
|
||||||
{
|
{
|
||||||
return width switch
|
return width switch
|
||||||
{
|
{
|
||||||
|
|
@ -36,17 +36,17 @@ namespace Ryujinx.HLE.HOS.Tamper
|
||||||
|
|
||||||
return comparison switch
|
return comparison switch
|
||||||
{
|
{
|
||||||
Comparison.Greater => CreateCore<CondGT<byte>>(),
|
Comparison.Greater => CreateCore<CondGTFactory>(),
|
||||||
Comparison.GreaterOrEqual => CreateCore<CondGE<byte>>(),
|
Comparison.GreaterOrEqual => CreateCore<CondGEFactory>(),
|
||||||
Comparison.Less => CreateCore<CondLT<byte>>(),
|
Comparison.Less => CreateCore<CondLTFactory>(),
|
||||||
Comparison.LessOrEqual => CreateCore<CondLE<byte>>(),
|
Comparison.LessOrEqual => CreateCore<CondLEFactory>(),
|
||||||
Comparison.Equal => CreateCore<CondEQ<byte>>(),
|
Comparison.Equal => CreateCore<CondEQFactory>(),
|
||||||
Comparison.NotEqual => CreateCore<CondNE<byte>>(),
|
Comparison.NotEqual => CreateCore<CondNEFactory>(),
|
||||||
_ => throw new TamperCompilationException($"Invalid comparison {comparison} in Atmosphere cheat"),
|
_ => throw new TamperCompilationException($"Invalid comparison {comparison} in Atmosphere cheat"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IOperation Create<TOp>(byte width, IOperand destination, IOperand lhs, IOperand rhs) where TOp : IOperation
|
public static IOperation Create<TOp>(byte width, IOperand destination, IOperand lhs, IOperand rhs) where TOp : IOperationFactory
|
||||||
{
|
{
|
||||||
return width switch
|
return width switch
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
interface IOperation
|
interface IOperation
|
||||||
{
|
{
|
||||||
void Execute();
|
void Execute();
|
||||||
|
|
||||||
static virtual IOperation CreateFor<T>(IOperand destination, IOperand lhs, IOperand rhs) where T : unmanaged, IBinaryInteger<T>
|
|
||||||
=> throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
src/Ryujinx.HLE/HOS/Tamper/Operations/IOperationFactory.cs
Normal file
10
src/Ryujinx.HLE/HOS/Tamper/Operations/IOperationFactory.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
|
{
|
||||||
|
interface IOperationFactory
|
||||||
|
{
|
||||||
|
static abstract IOperation CreateFor<T>(IOperand destination, IOperand lhs, IOperand rhs) where T : unmanaged, IBinaryInteger<T>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
|
sealed class OpAddFactory : IOperationFactory
|
||||||
|
{
|
||||||
|
private OpAddFactory() { }
|
||||||
|
|
||||||
|
public static IOperation CreateFor<T>(IOperand destination, IOperand lhs, IOperand rhs) where T : unmanaged, IBinaryInteger<T>
|
||||||
|
=> new OpAdd<T>(destination, lhs, rhs);
|
||||||
|
}
|
||||||
class OpAdd<T> : IOperation where T : unmanaged, INumber<T>
|
class OpAdd<T> : IOperation where T : unmanaged, INumber<T>
|
||||||
{
|
{
|
||||||
readonly IOperand _destination;
|
readonly IOperand _destination;
|
||||||
|
|
@ -19,8 +26,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
_destination.Set(_lhs.Get<T>() + _rhs.Get<T>());
|
_destination.Set(_lhs.Get<T>() + _rhs.Get<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IOperation CreateFor<T1>(IOperand destination, IOperand lhs, IOperand rhs) where T1 : unmanaged, IBinaryInteger<T1>
|
|
||||||
=> new OpAdd<T1>(destination, lhs, rhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
|
sealed class OpAndFactory : IOperationFactory
|
||||||
|
{
|
||||||
|
private OpAndFactory() { }
|
||||||
|
|
||||||
|
public static IOperation CreateFor<T>(IOperand destination, IOperand lhs, IOperand rhs) where T : unmanaged, IBinaryInteger<T>
|
||||||
|
=> new OpAnd<T>(destination, lhs, rhs);
|
||||||
|
}
|
||||||
class OpAnd<T> : IOperation where T : unmanaged, IBinaryNumber<T>
|
class OpAnd<T> : IOperation where T : unmanaged, IBinaryNumber<T>
|
||||||
{
|
{
|
||||||
readonly IOperand _destination;
|
readonly IOperand _destination;
|
||||||
|
|
@ -19,8 +26,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
_destination.Set(_lhs.Get<T>() & _rhs.Get<T>());
|
_destination.Set(_lhs.Get<T>() & _rhs.Get<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IOperation CreateFor<T1>(IOperand destination, IOperand lhs, IOperand rhs) where T1 : unmanaged, IBinaryInteger<T1>
|
|
||||||
=> new OpAnd<T1>(destination, lhs, rhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
|
sealed class OpLshFactory : IOperationFactory
|
||||||
|
{
|
||||||
|
private OpLshFactory() { }
|
||||||
|
|
||||||
|
public static IOperation CreateFor<T>(IOperand destination, IOperand lhs, IOperand rhs) where T : unmanaged, IBinaryInteger<T>
|
||||||
|
=> new OpLsh<T>(destination, lhs, rhs);
|
||||||
|
}
|
||||||
class OpLsh<T> : IOperation where T : unmanaged, IBinaryInteger<T>
|
class OpLsh<T> : IOperation where T : unmanaged, IBinaryInteger<T>
|
||||||
{
|
{
|
||||||
readonly IOperand _destination;
|
readonly IOperand _destination;
|
||||||
|
|
@ -19,8 +26,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
_destination.Set(_lhs.Get<T>() << int.CreateTruncating(_rhs.Get<T>()));
|
_destination.Set(_lhs.Get<T>() << int.CreateTruncating(_rhs.Get<T>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IOperation CreateFor<T1>(IOperand destination, IOperand lhs, IOperand rhs) where T1 : unmanaged, IBinaryInteger<T1>
|
|
||||||
=> new OpLsh<T1>(destination, lhs, rhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
|
sealed class OpMovFactory : IOperationFactory
|
||||||
|
{
|
||||||
|
private OpMovFactory() { }
|
||||||
|
|
||||||
|
public static IOperation CreateFor<T>(IOperand destination, IOperand lhs, IOperand rhs) where T : unmanaged, IBinaryInteger<T>
|
||||||
|
=> new OpMov<T>(destination, lhs);
|
||||||
|
}
|
||||||
class OpMov<T> : IOperation where T : unmanaged, INumber<T>
|
class OpMov<T> : IOperation where T : unmanaged, INumber<T>
|
||||||
{
|
{
|
||||||
readonly IOperand _destination;
|
readonly IOperand _destination;
|
||||||
|
|
@ -17,8 +24,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
_destination.Set(_source.Get<T>());
|
_destination.Set(_source.Get<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IOperation CreateFor<T1>(IOperand destination, IOperand lhs, IOperand rhs) where T1 : unmanaged, IBinaryInteger<T1>
|
|
||||||
=> new OpMov<T1>(destination, lhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
|
sealed class OpMulFactory : IOperationFactory
|
||||||
|
{
|
||||||
|
private OpMulFactory() { }
|
||||||
|
|
||||||
|
public static IOperation CreateFor<T>(IOperand destination, IOperand lhs, IOperand rhs) where T : unmanaged, IBinaryInteger<T>
|
||||||
|
=> new OpMul<T>(destination, lhs, rhs);
|
||||||
|
}
|
||||||
class OpMul<T> : IOperation where T : unmanaged, INumber<T>
|
class OpMul<T> : IOperation where T : unmanaged, INumber<T>
|
||||||
{
|
{
|
||||||
readonly IOperand _destination;
|
readonly IOperand _destination;
|
||||||
|
|
@ -19,8 +26,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
_destination.Set(_lhs.Get<T>() * _rhs.Get<T>());
|
_destination.Set(_lhs.Get<T>() * _rhs.Get<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IOperation CreateFor<T1>(IOperand destination, IOperand lhs, IOperand rhs) where T1 : unmanaged, IBinaryInteger<T1>
|
|
||||||
=> new OpMul<T1>(destination, lhs, rhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
|
sealed class OpNotFactory : IOperationFactory
|
||||||
|
{
|
||||||
|
private OpNotFactory() { }
|
||||||
|
|
||||||
|
public static IOperation CreateFor<T>(IOperand destination, IOperand lhs, IOperand rhs) where T : unmanaged, IBinaryInteger<T>
|
||||||
|
=> new OpNot<T>(destination, lhs);
|
||||||
|
}
|
||||||
class OpNot<T> : IOperation where T : unmanaged, IBinaryNumber<T>
|
class OpNot<T> : IOperation where T : unmanaged, IBinaryNumber<T>
|
||||||
{
|
{
|
||||||
readonly IOperand _destination;
|
readonly IOperand _destination;
|
||||||
|
|
@ -17,8 +24,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
_destination.Set(~_source.Get<T>());
|
_destination.Set(~_source.Get<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IOperation CreateFor<T1>(IOperand destination, IOperand lhs, IOperand rhs) where T1 : unmanaged, IBinaryInteger<T1>
|
|
||||||
=> new OpNot<T1>(destination, lhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
|
sealed class OpOrFactory : IOperationFactory
|
||||||
|
{
|
||||||
|
private OpOrFactory() { }
|
||||||
|
|
||||||
|
public static IOperation CreateFor<T>(IOperand destination, IOperand lhs, IOperand rhs) where T : unmanaged, IBinaryInteger<T>
|
||||||
|
=> new OpOr<T>(destination, lhs, rhs);
|
||||||
|
}
|
||||||
class OpOr<T> : IOperation where T : unmanaged, IBinaryNumber<T>
|
class OpOr<T> : IOperation where T : unmanaged, IBinaryNumber<T>
|
||||||
{
|
{
|
||||||
readonly IOperand _destination;
|
readonly IOperand _destination;
|
||||||
|
|
@ -19,8 +26,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
_destination.Set(_lhs.Get<T>() | _rhs.Get<T>());
|
_destination.Set(_lhs.Get<T>() | _rhs.Get<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IOperation CreateFor<T1>(IOperand destination, IOperand lhs, IOperand rhs) where T1 : unmanaged, IBinaryInteger<T1>
|
|
||||||
=> new OpOr<T1>(destination, lhs, rhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
|
sealed class OpRshFactory : IOperationFactory
|
||||||
|
{
|
||||||
|
private OpRshFactory() { }
|
||||||
|
|
||||||
|
public static IOperation CreateFor<T>(IOperand destination, IOperand lhs, IOperand rhs) where T : unmanaged, IBinaryInteger<T>
|
||||||
|
=> new OpRsh<T>(destination, lhs, rhs);
|
||||||
|
}
|
||||||
class OpRsh<T> : IOperation where T : unmanaged, IBinaryInteger<T>
|
class OpRsh<T> : IOperation where T : unmanaged, IBinaryInteger<T>
|
||||||
{
|
{
|
||||||
readonly IOperand _destination;
|
readonly IOperand _destination;
|
||||||
|
|
@ -19,8 +26,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
_destination.Set(_lhs.Get<T>() >> int.CreateTruncating(_rhs.Get<T>()));
|
_destination.Set(_lhs.Get<T>() >> int.CreateTruncating(_rhs.Get<T>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IOperation CreateFor<T1>(IOperand destination, IOperand lhs, IOperand rhs) where T1 : unmanaged, IBinaryInteger<T1>
|
|
||||||
=> new OpRsh<T1>(destination, lhs, rhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
|
sealed class OpSubFactory : IOperationFactory
|
||||||
|
{
|
||||||
|
private OpSubFactory() { }
|
||||||
|
|
||||||
|
public static IOperation CreateFor<T>(IOperand destination, IOperand lhs, IOperand rhs) where T : unmanaged, IBinaryInteger<T>
|
||||||
|
=> new OpSub<T>(destination, lhs, rhs);
|
||||||
|
}
|
||||||
class OpSub<T> : IOperation where T : unmanaged, INumber<T>
|
class OpSub<T> : IOperation where T : unmanaged, INumber<T>
|
||||||
{
|
{
|
||||||
readonly IOperand _destination;
|
readonly IOperand _destination;
|
||||||
|
|
@ -19,8 +26,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
_destination.Set(_lhs.Get<T>() - _rhs.Get<T>());
|
_destination.Set(_lhs.Get<T>() - _rhs.Get<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IOperation CreateFor<T1>(IOperand destination, IOperand lhs, IOperand rhs) where T1 : unmanaged, IBinaryInteger<T1>
|
|
||||||
=> new OpSub<T1>(destination, lhs, rhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
|
sealed class OpXorFactory : IOperationFactory
|
||||||
|
{
|
||||||
|
private OpXorFactory() { }
|
||||||
|
|
||||||
|
public static IOperation CreateFor<T>(IOperand destination, IOperand lhs, IOperand rhs) where T : unmanaged, IBinaryInteger<T>
|
||||||
|
=> new OpXor<T>(destination, lhs, rhs);
|
||||||
|
}
|
||||||
class OpXor<T> : IOperation where T : unmanaged, IBinaryNumber<T>
|
class OpXor<T> : IOperation where T : unmanaged, IBinaryNumber<T>
|
||||||
{
|
{
|
||||||
readonly IOperand _destination;
|
readonly IOperand _destination;
|
||||||
|
|
@ -19,8 +26,5 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations
|
||||||
{
|
{
|
||||||
_destination.Set(_lhs.Get<T>() ^ _rhs.Get<T>());
|
_destination.Set(_lhs.Get<T>() ^ _rhs.Get<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IOperation CreateFor<T1>(IOperand destination, IOperand lhs, IOperand rhs) where T1 : unmanaged, IBinaryInteger<T1>
|
|
||||||
=> new OpXor<T1>(destination, lhs, rhs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue