├── .gitignore ├── CheatASM.sln ├── CheatASM ├── Assembler.cs ├── CheatASM.csproj ├── CheatASM.g4 ├── CheatTypes.cs ├── Disassembler.cs ├── Opcodes │ ├── Opcode0StoreStaticToMemory.cs │ ├── Opcode1Conditional.cs │ ├── Opcode2EndConditional.cs │ ├── Opcode3Loop.cs │ ├── Opcode4LoadRegWithStatic.cs │ ├── Opcode5LoadRegWithMem.cs │ ├── Opcode6StoreStaticToAddress.cs │ ├── Opcode7LegacyArithmetic.cs │ ├── Opcode8KeypressConditional.cs │ ├── Opcode9Arithmetic.cs │ ├── OpcodeAStoreRegToAddress.cs │ ├── OpcodeC0RegisterConditional.cs │ ├── OpcodeC1SaveRestoreReg.cs │ ├── OpcodeC2SaveRestoreRegMask.cs │ ├── OpcodeC3ReadWriteStaticReg.cs │ ├── OpcodeFF0PauseProcess.cs │ ├── OpcodeFF1ResumeProcess.cs │ └── OpcodeFFFDebugLog.cs ├── Options.cs ├── Program.cs ├── Properties │ ├── PublishProfiles │ │ └── FolderProfile.pubxml │ └── launchSettings.json └── examples │ ├── C1C2C3.asm │ ├── asm_skeleton.asm │ ├── instructions_only.asm │ └── variables.asm ├── CheatASMTests ├── AssemblerTests.cs ├── CheatASMTests.csproj └── DisassemblerTests.cs ├── LICENSE ├── README.md └── publish.bat /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.*~ 3 | project.lock.json 4 | .DS_Store 5 | *.pyc 6 | nupkg/ 7 | 8 | # Visual Studio Code 9 | .vscode 10 | 11 | # Rider 12 | .idea 13 | 14 | # User-specific files 15 | *.suo 16 | *.user 17 | *.userosscache 18 | *.sln.docstates 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | build/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Oo]ut/ 32 | msbuild.log 33 | msbuild.err 34 | msbuild.wrn 35 | 36 | # Visual Studio 2015 37 | .vs/ 38 | *.ncrunchproject 39 | *.ncrunchsolution 40 | -------------------------------------------------------------------------------- /CheatASM.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28705.295 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CheatASM", "CheatASM\CheatASM.csproj", "{05BC400E-582B-4D24-BE51-2D122B6FFB55}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CheatASMTests", "CheatASMTests\CheatASMTests.csproj", "{1578E7C8-7008-41E4-8B76-20B5ED5F65F3}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {05BC400E-582B-4D24-BE51-2D122B6FFB55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {05BC400E-582B-4D24-BE51-2D122B6FFB55}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {05BC400E-582B-4D24-BE51-2D122B6FFB55}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {05BC400E-582B-4D24-BE51-2D122B6FFB55}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {1578E7C8-7008-41E4-8B76-20B5ED5F65F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {1578E7C8-7008-41E4-8B76-20B5ED5F65F3}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {1578E7C8-7008-41E4-8B76-20B5ED5F65F3}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {1578E7C8-7008-41E4-8B76-20B5ED5F65F3}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {5F1D5B3F-8704-4D2B-B26E-045F774CBC8A} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /CheatASM/Assembler.cs: -------------------------------------------------------------------------------- 1 | using Antlr4.Runtime; 2 | using Antlr4.Runtime.Misc; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Text; 10 | using static CheatASM.CheatASMParser; 11 | 12 | namespace CheatASM 13 | { 14 | 15 | public enum AnyRefType { NUMBER, REGISTER } 16 | 17 | public class VariableDeclaration 18 | { 19 | public string Name; 20 | public bool Const; 21 | public string Value; 22 | public string Type; 23 | } 24 | 25 | public class AssemblerException : Exception 26 | { 27 | public AssemblerException(string str) : base(str) { } 28 | } 29 | 30 | 31 | public class AssemblyResult 32 | { 33 | public string GameInfo; 34 | public string TitleID; 35 | public string BuildID; 36 | public List Cheats = new List(); 37 | 38 | 39 | public override string ToString() 40 | { 41 | StringBuilder sb = new StringBuilder(); 42 | if (GameInfo != null) 43 | { 44 | sb.AppendLine("[" + GameInfo + "]"); 45 | 46 | } else 47 | { 48 | sb.AppendLine("[Assembled by CheatASM]"); 49 | } 50 | /* print out master code first if present */ 51 | var masterCode = Cheats.Where(c => c.IsMaster).FirstOrDefault(); 52 | if (masterCode != null) 53 | { 54 | sb.Append("{").Append(masterCode.Name).Append("}\r\n"); 55 | 56 | foreach (var opcode in masterCode.VarInitCodes) 57 | { 58 | sb.AppendLine(opcode.ToByteString()); 59 | } 60 | 61 | foreach (var opcode in masterCode.Opcodes) 62 | { 63 | sb.AppendLine(opcode.ToByteString()); 64 | } 65 | sb.AppendLine(); 66 | } 67 | 68 | foreach (var cheat in Cheats.Where(c => c.IsMaster == false)) 69 | { 70 | sb.Append("[").Append(cheat.Name).Append("]\r\n"); 71 | foreach(var opcode in cheat.VarInitCodes) 72 | { 73 | sb.AppendLine(opcode.ToByteString()); 74 | } 75 | foreach (var opcode in cheat.Opcodes) 76 | { 77 | sb.AppendLine(opcode.ToByteString()); 78 | } 79 | sb.AppendLine(); 80 | } 81 | return sb.ToString(); 82 | } 83 | } 84 | 85 | public class Cheat 86 | { 87 | public bool IsMaster; 88 | public List Opcodes = new List(); 89 | public List VarInitCodes = new List(); 90 | internal string Name; 91 | 92 | internal Dictionary VariableReg = new Dictionary(); 93 | 94 | } 95 | 96 | public class Assembler : BaseErrorListener 97 | { 98 | static string[] RegisterList = { "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "RA", "RB", "RC", "RD", "RE", "RF" }; 99 | public Dictionary Variables = new Dictionary(); 100 | HashSet SeenRegisters = new HashSet(); 101 | string parsingContent = null; 102 | string errorMsg; 103 | int errorPos; 104 | static Dictionary CompareOperatorMap = new Dictionary() 105 | { 106 | {"<",ConditionalComparisonType.lt }, 107 | {"<=",ConditionalComparisonType.le }, 108 | {">",ConditionalComparisonType.gt }, 109 | {">=",ConditionalComparisonType.ge }, 110 | {"==",ConditionalComparisonType.eq }, 111 | {"!=",ConditionalComparisonType.ne }, 112 | }; 113 | 114 | static Dictionary InvertedCompareOperatorMap = new Dictionary() 115 | { 116 | {"<",ConditionalComparisonType.ge }, 117 | {"<=",ConditionalComparisonType.gt }, 118 | {">",ConditionalComparisonType.le }, 119 | {">=",ConditionalComparisonType.lt }, 120 | {"==",ConditionalComparisonType.ne }, 121 | {"!=",ConditionalComparisonType.eq }, 122 | }; 123 | 124 | 125 | public override void SyntaxError([NotNull] IRecognizer recognizer, [Nullable] IToken offendingSymbol, int line, int charPositionInLine, [NotNull] string msg, [Nullable] RecognitionException e) 126 | { 127 | base.SyntaxError(recognizer, offendingSymbol, line, charPositionInLine, msg, e); 128 | errorMsg = msg; 129 | errorPos = charPositionInLine; 130 | } 131 | 132 | 133 | public AssemblyResult AssembleFile(string filePath) 134 | { 135 | return AssembleString(File.ReadAllText(filePath)); 136 | 137 | } 138 | 139 | private string PreProcessConstants(string contents) 140 | { 141 | 142 | AntlrInputStream stream = new AntlrInputStream(contents); 143 | CheatASMLexer lexer = new CheatASMLexer(stream); 144 | 145 | CheatASMParser parser = new CheatASMParser(new CommonTokenStream(lexer)); 146 | parser.ErrorHandler = new DefaultErrorStrategy(); 147 | parser.TrimParseTree = true; 148 | parser.BuildParseTree = true; 149 | parser.AddErrorListener(this); 150 | errorMsg = null; 151 | errorPos = 0; 152 | 153 | ProgramContext prog = parser.program(); 154 | Dictionary constantValues = new(); 155 | int lastConstantStopIndex = 0; 156 | foreach (var decl in prog.variableDecl()) 157 | { 158 | if (decl.@const != null) 159 | { 160 | /* its a constant! */ 161 | constantValues.Add(decl.name.Text, decl.val.start.Text); 162 | lastConstantStopIndex = decl.Stop.StopIndex; 163 | } 164 | } 165 | /* parse all of the tokens again */ 166 | 167 | stream = new AntlrInputStream(contents); 168 | lexer = new CheatASMLexer(stream); 169 | 170 | var tokenStream = new CommonTokenStream(lexer); 171 | tokenStream.Fill(); 172 | 173 | var variableTokens = tokenStream.GetTokens().Where(t => t.Type == CheatASMLexer.VARIABLE_NAME && t.StartIndex > lastConstantStopIndex).OrderByDescending(t => t.StartIndex).ToList(); 174 | StringBuilder sb = new StringBuilder(contents); 175 | foreach(var variableName in variableTokens) 176 | { 177 | if (constantValues.ContainsKey(variableName.Text)) 178 | { 179 | sb.Remove(variableName.StartIndex, variableName.Text.Length); 180 | sb.Insert(variableName.StartIndex, constantValues[variableName.Text]); 181 | } 182 | } 183 | 184 | contents = sb.ToString(); 185 | return contents; 186 | } 187 | 188 | public AssemblyResult AssembleString(string contents) 189 | { 190 | 191 | contents = PreProcessConstants(contents); 192 | parsingContent = contents; 193 | AntlrInputStream stream = new AntlrInputStream(contents); 194 | CheatASMLexer lexer = new CheatASMLexer(stream); 195 | 196 | CheatASMParser parser = new CheatASMParser(new CommonTokenStream(lexer)); 197 | parser.ErrorHandler = new DefaultErrorStrategy(); 198 | parser.TrimParseTree = true; 199 | parser.BuildParseTree = true; 200 | parser.AddErrorListener(this); 201 | errorMsg = null; 202 | errorPos = 0; 203 | ProgramContext prog = parser.program(); 204 | 205 | AssemblyResult result = new AssemblyResult(); 206 | 207 | if (prog.gameInfo() != null) 208 | { 209 | result.GameInfo = prog.gameInfo().info.Text.Substring(1, prog.gameInfo().info.Text.Length - 2); 210 | } 211 | 212 | if (prog.titleID() != null) 213 | { 214 | result.TitleID = prog.titleID().title.Text.Substring(1, prog.titleID().title.Text.Length - 2); 215 | } 216 | 217 | if (prog.buildID() != null) 218 | { 219 | result.BuildID = prog.buildID().build.Text.Substring(1, prog.buildID().build.Text.Length - 2); 220 | } 221 | 222 | if (prog.variableDecl() != null && prog.variableDecl().Length > 0) 223 | { 224 | /* register all variables */ 225 | foreach (var varCtx in prog.variableDecl()) 226 | { 227 | RegisterVariable(varCtx); 228 | } 229 | } 230 | 231 | if (prog.cheatEntry() != null && prog.cheatEntry().Length > 0) 232 | { 233 | /* has cheat entry */ 234 | foreach (var entry in prog.cheatEntry()) 235 | { 236 | var cheat = new Cheat(); 237 | 238 | cheat.Name = entry.cheatHeader().name.Text.Replace("\"", ""); 239 | if (entry.cheatHeader().master != null) 240 | { 241 | cheat.IsMaster = true; 242 | } 243 | 244 | var statements = entry.statementList().statement(); 245 | foreach (var stmt in statements) 246 | { 247 | AssembleStatement(stmt, cheat); 248 | } 249 | result.Cheats.Add(cheat); 250 | } 251 | 252 | } 253 | else if (prog.statementList().statement() != null && prog.statementList().statement().Length > 0) 254 | { 255 | var cheat = new Cheat(); 256 | cheat.Name = "Untitled"; 257 | foreach (var stmt in prog.statementList().statement()) 258 | { 259 | AssembleStatement(stmt, cheat); 260 | } 261 | result.Cheats.Add(cheat); 262 | } 263 | 264 | if (errorMsg != null) 265 | { 266 | throw new AssemblerException(errorMsg); 267 | } 268 | 269 | return result; 270 | } 271 | 272 | public string AssembleSingleInstruction(string instr) 273 | { 274 | var result = AssembleString(instr); 275 | return result.Cheats[0].Opcodes[0].ToByteString(); 276 | } 277 | 278 | private string ParseAnyRef(AnyRefContext anyRef, AnyRefType targetType, Cheat cheat) 279 | { 280 | var regVal = anyRef.reg; 281 | var numVal = anyRef.num; 282 | var varVal = anyRef.var; 283 | 284 | switch (targetType) 285 | { 286 | case AnyRefType.NUMBER: 287 | 288 | return ParseNumRef(new NumRefContext(null, 0) { num = numVal, var = varVal }); 289 | case AnyRefType.REGISTER: 290 | return ParseRegRef(new RegRefContext(null, 0) { reg = regVal, var = varVal }, cheat); 291 | } 292 | 293 | throw new AssemblerException("Failed to parse Any Ref"); 294 | } 295 | 296 | 297 | private string ParseRegRef(RegRefContext regRef, Cheat cheat) 298 | { 299 | if (regRef.reg != null) 300 | { 301 | if (cheat.VariableReg.ContainsValue(regRef.reg.Text)) 302 | { 303 | throw new AssemblerException("Explicit usage of register " + regRef.reg.Text + " while it is assigned to variable: " + cheat.VariableReg.Where(s => s.Value == regRef.reg.Text).First()); 304 | } 305 | return regRef.reg.Text; 306 | } 307 | else 308 | { 309 | var variableName = regRef.var.Text; 310 | VariableDeclaration variable = null; 311 | if (Variables.ContainsKey(variableName)) 312 | { 313 | variable = Variables[variableName]; 314 | } 315 | if (variable != null) 316 | { 317 | 318 | /* found a variable */ 319 | if (cheat.VariableReg.ContainsKey(variable)) 320 | { 321 | return cheat.VariableReg[variable]; 322 | } 323 | else 324 | { 325 | /* variable isn't in the map for this cheat yet... we need to add it */ 326 | 327 | /* find a register that isn't in use yet... */ 328 | var availableReg = RegisterList.Where(r => cheat.VariableReg.ContainsValue(r) == false).LastOrDefault(); 329 | if (String.IsNullOrEmpty(availableReg)) 330 | { 331 | throw new AssemblerException("Unable to find an unused variable for variable: " + variable.Name + "."); 332 | } 333 | else 334 | { 335 | /* add this variable to the map */ 336 | cheat.VariableReg.Add(variable, availableReg); 337 | 338 | /* create the variable init opcode */ 339 | var loadReg = new Opcode4LoadRegWithStatic(); 340 | loadReg.RegisterIndex = Convert.ToUInt32(availableReg.Substring(1), 16); 341 | loadReg.Value = Convert.ToUInt64(ConvertVariableValue(variable), 16); 342 | 343 | cheat.VarInitCodes.Add(loadReg); 344 | return availableReg; 345 | } 346 | 347 | } 348 | } 349 | else 350 | { 351 | // TODO: Support line numbers for errors 352 | throw new AssemblerException("Variable: " + variableName + " not defined."); 353 | } 354 | } 355 | } 356 | 357 | private string ConvertVariableValue(VariableDeclaration variable) 358 | { 359 | bool isValueHex = variable.Value.StartsWith("0x", StringComparison.CurrentCultureIgnoreCase); 360 | byte[] bytes = new byte[0]; 361 | switch(variable.Type) 362 | { 363 | case ".u8": 364 | var byteVal = Convert.ToByte(variable.Value, (isValueHex ? 16 : 10)); 365 | bytes = BitConverter.GetBytes(byteVal); 366 | break; 367 | case ".s8": 368 | var sbyteVal = Convert.ToSByte(variable.Value, (isValueHex ? 16 : 10)); 369 | bytes = BitConverter.GetBytes(sbyteVal); 370 | break; 371 | case ".u16": 372 | var shortVal = Convert.ToUInt16(variable.Value, (isValueHex ? 16 : 10)); 373 | bytes = BitConverter.GetBytes(shortVal); 374 | break; 375 | case ".s16": 376 | var sshortVal = Convert.ToInt16(variable.Value, (isValueHex ? 16 : 10)); 377 | bytes = BitConverter.GetBytes(sshortVal); 378 | break; 379 | case ".u32": 380 | var intVal = Convert.ToUInt32(variable.Value, (isValueHex ? 16 : 10)); 381 | bytes = BitConverter.GetBytes(intVal); 382 | break; 383 | case ".s32": 384 | var sintVal = Convert.ToInt32(variable.Value, (isValueHex ? 16 : 10)); 385 | bytes = BitConverter.GetBytes(sintVal); 386 | break; 387 | case ".u64": 388 | var longVal = Convert.ToUInt64(variable.Value, (isValueHex ? 16 : 10)); 389 | bytes = BitConverter.GetBytes(longVal); 390 | break; 391 | case ".s64": 392 | var slongVal = Convert.ToInt64(variable.Value, (isValueHex ? 16 : 10)); 393 | bytes = BitConverter.GetBytes(slongVal); 394 | break; 395 | case ".f32": 396 | float sVal; 397 | if (isValueHex) 398 | { 399 | sVal = Convert.ToSingle(Convert.ToUInt32(variable.Value, 16)); 400 | } 401 | else 402 | { 403 | sVal = Convert.ToSingle(variable.Value); 404 | } 405 | bytes = BitConverter.GetBytes(sVal); 406 | break; 407 | case ".f64": 408 | double doubleVal; 409 | if (isValueHex) 410 | { 411 | doubleVal = Convert.ToDouble(Convert.ToUInt64(variable.Value, 16)); 412 | } 413 | else 414 | { 415 | doubleVal = Convert.ToDouble(variable.Value); 416 | } 417 | bytes = BitConverter.GetBytes(doubleVal); 418 | break; 419 | } 420 | 421 | bytes.Reverse(); 422 | return "0x" + BitConverter.ToString(bytes.Reverse().ToArray()).Replace("-", ""); 423 | } 424 | 425 | private string ParseNumRef(NumRefContext numRef) 426 | { 427 | if (numRef.num != null) 428 | { 429 | return numRef.num.Text; 430 | } 431 | else 432 | { 433 | var variableName = numRef.var.Text; 434 | VariableDeclaration variable = null; 435 | if (Variables.ContainsKey(variableName)) 436 | { 437 | variable = Variables[variableName]; 438 | } 439 | if (variable != null) 440 | { 441 | /* found a variable */ 442 | if (variable.Const != true) 443 | { 444 | /* variable is a constant, but used as a register... bad */ 445 | throw new AssemblerException("Variable '" + variableName + "' is not declared constant, but used like a constant number."); 446 | } 447 | else 448 | { 449 | return ConvertVariableValue(variable); 450 | } 451 | } 452 | else 453 | { 454 | // TODO: Support line numbers for errors 455 | throw new AssemblerException("Variable '" + variableName + "' not defined before line XYZ"); 456 | } 457 | } 458 | } 459 | 460 | public AnyRefType GetAnyRefType(AnyRefContext ctx) 461 | { 462 | if (ctx.num != null) 463 | { 464 | return AnyRefType.NUMBER; 465 | } else if (ctx.reg != null) 466 | { 467 | return AnyRefType.REGISTER; 468 | } else if (ctx.var != null) 469 | { 470 | /* we have a variable... */ 471 | VariableDeclaration variable = null; 472 | if (Variables.ContainsKey(ctx.var.Text)) 473 | { 474 | variable = Variables[ctx.var.Text]; 475 | } 476 | if (variable == null) 477 | { 478 | throw new AssemblerException("Use of undefined variable: " + ctx.var.Text); 479 | } 480 | if (variable.Const) 481 | { 482 | return AnyRefType.NUMBER; 483 | } else 484 | { 485 | return AnyRefType.REGISTER; 486 | } 487 | } else 488 | { 489 | throw new AssemblerException("Unable to determine type of AnyRef: " + ctx.ToString()); 490 | } 491 | } 492 | 493 | public void RegisterVariable(VariableDeclContext varCtx) 494 | { 495 | 496 | VariableDeclaration decl = new VariableDeclaration(); 497 | 498 | decl.Name = varCtx.name.Text; 499 | decl.Type = varCtx.type.Text; 500 | decl.Const = (varCtx.@const != null); 501 | decl.Value = varCtx.val.GetText(); 502 | 503 | if (Variables.ContainsKey(decl.Name)) 504 | { 505 | throw new AssemblerException("Variable '" + decl.Name + "' already defined."); 506 | } 507 | 508 | Variables.Add(decl.Name, decl); 509 | } 510 | 511 | private void AssembleOpCode6(OpCode6Context opCtx, Cheat cheat) 512 | { 513 | /* should be an opcode 6... */ 514 | Opcode6StoreStaticToAddress opTyped = new Opcode6StoreStaticToAddress(); 515 | 516 | opTyped.BitWidth = Enum.Parse(opCtx.bitWidth.Text, true); 517 | opTyped.RegisterIndex = Convert.ToUInt16(ParseRegRef(opCtx.@base, cheat).Substring(1), 16); 518 | 519 | if (opCtx.increment != null) 520 | { 521 | opTyped.IncrementFlag = true; 522 | } 523 | if (opCtx.regOffset != null) 524 | { 525 | opTyped.OffsetEnableFlag = true; 526 | opTyped.OffsetRegister = Convert.ToUInt16(ParseRegRef(opCtx.regOffset, cheat).Substring(1), 16); 527 | } 528 | 529 | opTyped.Value = Convert.ToUInt64(ParseNumRef(opCtx.value), 16); 530 | CheckValueFitsBitWidth(opTyped.BitWidth, opTyped.Value); 531 | cheat.Opcodes.Add(opTyped); 532 | } 533 | 534 | private void AssembleOpCodeA(OpCodeAContext opCtx, Cheat cheat) 535 | { 536 | OpcodeAStoreRegToAddress opTyped = new OpcodeAStoreRegToAddress(); 537 | 538 | opTyped.BitWidth = Enum.Parse(opCtx.bitWidth.Text, true); 539 | if (opCtx.@base != null) 540 | { 541 | opTyped.AddressRegister = Convert.ToUInt32(ParseRegRef(opCtx.@base, cheat).Substring(1), 16); 542 | } 543 | opTyped.SourceRegister = Convert.ToUInt32(ParseRegRef(opCtx.regValue, cheat).Substring(1), 16); 544 | if (opCtx.increment != null) 545 | { 546 | opTyped.IncrementFlag = true; 547 | } 548 | opTyped.OffsetType = 0; 549 | 550 | if (opCtx.memType != null) 551 | { 552 | opTyped.MemType = Enum.Parse(opCtx.memType.Text); 553 | /* has to be OffsetType 3,4,5 */ 554 | bool hasReg = opCtx.regOffset != null; 555 | bool hasVal = opCtx.numOffset != null; 556 | 557 | if (hasReg && hasVal) 558 | { 559 | /* type 5 */ 560 | opTyped.OffsetType = 5; 561 | opTyped.OffsetRegister = Convert.ToUInt16(ParseRegRef(opCtx.regOffset, cheat).Substring(1), 16); 562 | opTyped.RelativeAddress = Convert.ToUInt64(ParseNumRef(opCtx.numOffset), 16); 563 | } 564 | else if (hasReg) 565 | { 566 | /* type 3 */ 567 | opTyped.OffsetType = 3; 568 | opTyped.OffsetRegister = Convert.ToUInt16(ParseRegRef(opCtx.regOffset, cheat).Substring(1), 16); 569 | } 570 | else if (hasVal) 571 | { 572 | /* type 4 */ 573 | opTyped.OffsetType = 4; 574 | opTyped.RelativeAddress = Convert.ToUInt64(ParseNumRef(opCtx.numOffset), 16); 575 | } 576 | } 577 | else 578 | { 579 | /* has to be OffsetType 1,2 */ 580 | if (opCtx.regOffset != null) 581 | { 582 | opTyped.OffsetType = 1; 583 | opTyped.OffsetRegister = Convert.ToUInt16(ParseRegRef(opCtx.regOffset, cheat).Substring(1), 16); 584 | } else if (opCtx.numOffset != null) 585 | { 586 | opTyped.OffsetType = 2; 587 | opTyped.RelativeAddress = Convert.ToUInt64(ParseNumRef(opCtx.numOffset), 16); 588 | } 589 | } 590 | cheat.Opcodes.Add(opTyped); 591 | } 592 | 593 | 594 | private void AssembleOpCodeC1C2(OpCodeC1C2Context opCtx, Cheat cheat) 595 | { 596 | string func = opCtx.func.Text.ToLower(); 597 | string type = opCtx.type.Text.ToLower(); 598 | 599 | if (opCtx.index != null || opCtx.reg != null) 600 | { 601 | OpcodeC1SaveRestoreReg op = new OpcodeC1SaveRestoreReg(); 602 | switch (func) 603 | { 604 | case "load": 605 | op.OperandType = 0; 606 | break; 607 | case "save": 608 | op.OperandType = 1; 609 | break; 610 | case "clear": 611 | if (type.Equals("saved")) 612 | { 613 | op.OperandType = 2; 614 | } 615 | else if (type.Equals("reg")) 616 | { 617 | op.OperandType = 3; 618 | } 619 | break; 620 | } 621 | /* Op Code C1 */ 622 | switch (op.OperandType) { 623 | case 0: 624 | /* Restore */ 625 | op.SourceIndex = Convert.ToUInt32(ParseNumRef(opCtx.index), 16); 626 | op.DestinationIndex = Convert.ToUInt32(ParseRegRef(opCtx.reg, cheat).Substring(1), 16); 627 | break; 628 | case 1: 629 | /* Save */ 630 | op.DestinationIndex = Convert.ToUInt32(ParseNumRef(opCtx.index), 16); 631 | op.SourceIndex = Convert.ToUInt32(ParseRegRef(opCtx.reg, cheat).Substring(1), 16); 632 | break; 633 | case 2: 634 | /* Clear saved */ 635 | op.DestinationIndex = Convert.ToUInt32(ParseNumRef(opCtx.index), 16); 636 | break; 637 | case 3: 638 | /* clear register */ 639 | op.DestinationIndex = Convert.ToUInt32(ParseRegRef(opCtx.reg, cheat).Substring(1), 16); 640 | break; 641 | } 642 | cheat.Opcodes.Add(op); 643 | } else if (opCtx.regs != null || opCtx.indexes != null) 644 | { 645 | /* Op Code C2 */ 646 | OpcodeC2SaveRestoreRegMask op = new(); 647 | switch (func) 648 | { 649 | case "load": 650 | op.OperandType = 0; 651 | break; 652 | case "save": 653 | op.OperandType = 1; 654 | break; 655 | case "clear": 656 | if (type.Equals("saved")) 657 | { 658 | op.OperandType = 2; 659 | } 660 | else if (type.Equals("regs")) 661 | { 662 | op.OperandType = 3; 663 | } 664 | break; 665 | } 666 | 667 | bool[] maskBits = new bool[16]; 668 | 669 | if (opCtx.indexes != null) 670 | { 671 | foreach (var numRefCtx in opCtx.indexes.GetRuleContexts()) 672 | { 673 | var indexNum = Convert.ToUInt32(ParseNumRef(numRefCtx), 16); 674 | maskBits[indexNum] = true; 675 | } 676 | } else if (opCtx.regs != null) 677 | { 678 | foreach(var regRefCtx in opCtx.regs.GetRuleContexts()) 679 | { 680 | var regNum = Convert.ToUInt32(ParseRegRef(regRefCtx, cheat).Substring(1), 16); 681 | maskBits[regNum] = true; 682 | } 683 | } 684 | 685 | Array.Copy(maskBits, op.RegMask, 16); 686 | cheat.Opcodes.Add(op); 687 | } 688 | } 689 | 690 | private void AssembleOpCodeC3(OpCodeC3Context opCtx, Cheat cheat) 691 | { 692 | OpcodeC3ReadWriteStaticReg op = new(); 693 | op.WriteMode = opCtx.func.Text.ToLower().Equals("save"); 694 | 695 | op.StaticRegIndex = Convert.ToUInt32(opCtx.sreg.Text.Substring(2), 16); 696 | op.RegIndex = Convert.ToUInt32(ParseRegRef(opCtx.reg, cheat).Substring(1)); 697 | 698 | cheat.Opcodes.Add(op); 699 | } 700 | 701 | private void AssembleOpCodeFF0(OpCodeFF0Context opCtx, Cheat cheat) 702 | { 703 | cheat.Opcodes.Add(new OpcodeFF0PauseProcess()); 704 | } 705 | 706 | private void AssembleOpCodeFF1(OpCodeFF1Context opCtx, Cheat cheat) 707 | { 708 | cheat.Opcodes.Add(new OpcodeFF1ResumeProcess()); 709 | } 710 | 711 | private void AssembleOpCodeFFF(OpCodeFFFContext opCtx, Cheat cheat) 712 | { 713 | OpcodeFFFDebugLog op = new(); 714 | op.BitWidth = Enum.Parse(opCtx.bitWidth.Text, true); 715 | op.LogId = Convert.ToUInt32(opCtx.id.Text, 16); 716 | 717 | if (opCtx.memType != null) 718 | { 719 | op.MemType = Enum.Parse(opCtx.memType.Text); 720 | if (opCtx.offset != null) 721 | { 722 | var refType = GetAnyRefType(opCtx.offset); 723 | switch(refType) 724 | { 725 | case AnyRefType.NUMBER: 726 | op.OperandType = 0; 727 | op.RelativeAddress = Convert.ToUInt64(ParseAnyRef(opCtx.offset, AnyRefType.NUMBER, cheat), 16); 728 | break; 729 | case AnyRefType.REGISTER: 730 | op.OperandType = 1; 731 | op.OffsetRegister = Convert.ToUInt32(ParseAnyRef(opCtx.offset, AnyRefType.REGISTER, cheat).Substring(1), 16); 732 | break; 733 | } 734 | } 735 | } else if (opCtx.addrReg != null) 736 | { 737 | op.AddressRegister = Convert.ToUInt32(ParseRegRef(opCtx.addrReg, cheat).Substring(1), 16); 738 | if (opCtx.offset == null) 739 | { 740 | op.OperandType = 2; 741 | op.RelativeAddress = 0; 742 | } 743 | else 744 | { 745 | var refType = GetAnyRefType(opCtx.offset); 746 | switch (refType) 747 | { 748 | case AnyRefType.NUMBER: 749 | op.OperandType = 2; 750 | op.RelativeAddress = Convert.ToUInt64(ParseAnyRef(opCtx.offset, AnyRefType.NUMBER, cheat), 16); 751 | break; 752 | case AnyRefType.REGISTER: 753 | op.OperandType = 3; 754 | op.OffsetRegister = Convert.ToUInt32(ParseAnyRef(opCtx.offset, AnyRefType.REGISTER, cheat).Substring(1), 16); 755 | break; 756 | } 757 | } 758 | } else 759 | { 760 | op.OperandType = 4; 761 | op.ValueRegister = Convert.ToUInt32(ParseRegRef(opCtx.value, cheat).Substring(1), 16); 762 | } 763 | cheat.Opcodes.Add(op); 764 | } 765 | 766 | private void AssembleOpCodeC0(OpCodeC0Context opCtx, Cheat cheat) 767 | { 768 | /*opCodeC0: (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) (source=regRef) COMMA LSQUARE (memType=MEM_TYPE) PLUS_SIGN (offset=anyRef) RSQUARE 769 | | (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) (source=regRef) COMMA LSQUARE (addrReg=regRef) PLUS_SIGN (offset=anyRef) RSQUARE 770 | | (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) (source=regRef) COMMA (value=anyRef);*/ 771 | OpcodeC0RegisterConditional opTyped = new OpcodeC0RegisterConditional(); 772 | 773 | opTyped.BitWidth = Enum.Parse(opCtx.bitWidth.Text, true); 774 | opTyped.Condition = Enum.Parse(opCtx.cond.Text, true); 775 | opTyped.SourceRegister = Convert.ToUInt16(ParseRegRef(opCtx.source, cheat).Substring(1), 16); 776 | if (opCtx.memType != null) 777 | { 778 | opTyped.MemType = Enum.Parse(opCtx.memType.Text, true); 779 | 780 | /* operand type is either 0 or 1... */ 781 | if (opCtx.offset == null || GetAnyRefType(opCtx.offset) == AnyRefType.NUMBER) 782 | { 783 | if (opCtx.offset != null) 784 | { 785 | opTyped.RelativeAddress = Convert.ToUInt64(ParseAnyRef(opCtx.offset, AnyRefType.NUMBER, cheat), 16); 786 | } else 787 | { 788 | opTyped.RelativeAddress = 0; 789 | } 790 | opTyped.OperandType = 0; 791 | } 792 | else if (GetAnyRefType(opCtx.offset) == AnyRefType.REGISTER) 793 | { 794 | opTyped.OffsetRegister = Convert.ToUInt16(ParseAnyRef(opCtx.offset,AnyRefType.REGISTER, cheat).Substring(1), 16); 795 | opTyped.OperandType = 1; 796 | } 797 | } 798 | else if (opCtx.addrReg != null) 799 | { 800 | opTyped.AddressRegister = Convert.ToUInt32(ParseRegRef(opCtx.addrReg, cheat).Substring(1),16); 801 | /* operand type is either 2 or 3 */ 802 | if (opCtx.offset == null || GetAnyRefType(opCtx.offset) == AnyRefType.NUMBER) 803 | { 804 | if (opCtx.offset != null) 805 | { 806 | opTyped.RelativeAddress = Convert.ToUInt64(ParseAnyRef(opCtx.offset, AnyRefType.NUMBER, cheat), 16); 807 | } else 808 | { 809 | opTyped.RelativeAddress = 0; 810 | } 811 | opTyped.OperandType = 2; 812 | } 813 | else if (GetAnyRefType(opCtx.offset) == AnyRefType.REGISTER) 814 | { 815 | opTyped.OffsetRegister = Convert.ToUInt16(ParseAnyRef(opCtx.offset, AnyRefType.REGISTER, cheat).Substring(1), 16); 816 | opTyped.OperandType = 3; 817 | } 818 | } 819 | else if (opCtx.value != null) 820 | { 821 | /* operand type is either 2 or 3 */ 822 | if (GetAnyRefType(opCtx.value) == AnyRefType.NUMBER) 823 | { 824 | opTyped.Value = Convert.ToUInt64(ParseAnyRef(opCtx.value, AnyRefType.NUMBER, cheat), 16); 825 | CheckValueFitsBitWidth(opTyped.BitWidth, opTyped.Value); 826 | opTyped.OperandType = 4; 827 | } 828 | else if (GetAnyRefType(opCtx.value) == AnyRefType.REGISTER) 829 | { 830 | opTyped.OtherRegister = Convert.ToUInt16(ParseAnyRef(opCtx.value, AnyRefType.REGISTER, cheat).Substring(1), 16); 831 | opTyped.OperandType = 5; 832 | } 833 | } 834 | cheat.Opcodes.Add(opTyped); 835 | } 836 | 837 | private void AssembleOpCode9(OpCode9Context opCtx, Cheat cheat) 838 | { 839 | Opcode9Arithmetic opTyped = new Opcode9Arithmetic(); 840 | 841 | 842 | opTyped.BitWidth = Enum.Parse(opCtx.bitWidth.Text, true); 843 | opTyped.RegisterDest = Convert.ToUInt16(ParseRegRef(opCtx.dest, cheat).Substring(1), 16); 844 | opTyped.MathType = Enum.Parse(opCtx.func.Text, true); 845 | 846 | opTyped.RegisterLeft = Convert.ToUInt16(ParseRegRef(opCtx.leftReg, cheat).Substring(1), 16); 847 | 848 | if (opCtx.right != null) 849 | { 850 | if (GetAnyRefType(opCtx.right) == AnyRefType.NUMBER) 851 | { 852 | opTyped.RightHandRegister = false; 853 | opTyped.Value = opTyped.Value = Convert.ToUInt64(ParseAnyRef(opCtx.right, AnyRefType.NUMBER, cheat), 16); 854 | CheckValueFitsBitWidth(opTyped.BitWidth, opTyped.Value); 855 | } 856 | else if (GetAnyRefType(opCtx.right) == AnyRefType.REGISTER) 857 | { 858 | opTyped.RightHandRegister = true; 859 | opTyped.RegisterRight = Convert.ToUInt16(ParseAnyRef(opCtx.right, AnyRefType.REGISTER, cheat).Substring(1), 16); 860 | } 861 | } else 862 | { 863 | opTyped.NoRightHandOperand = true; 864 | } 865 | 866 | cheat.Opcodes.Add(opTyped); 867 | } 868 | 869 | private void AssembleOpCode8(OpCode8Context opCtx, Cheat cheat) 870 | { 871 | Opcode8KeypressConditional opTyped = new Opcode8KeypressConditional(); 872 | 873 | try 874 | { 875 | opTyped.Mask = Enum.Parse(opCtx.key.Text); 876 | } 877 | catch (Exception ex) 878 | { 879 | throw new AssemblerException($"On line: {opCtx.Start.Line} instruction: \"{GetCurrentInstructionText(opCtx)}\", The key \"{opCtx.key.Text}\" is not valid."); 880 | } 881 | cheat.Opcodes.Add(opTyped); 882 | } 883 | 884 | private void AssembleOpCode7(OpCode7Context opCtx, Cheat cheat) 885 | { 886 | Opcode7LegacyArithmetic opTyped = new Opcode7LegacyArithmetic(); 887 | 888 | 889 | opTyped.BitWidth = Enum.Parse(opCtx.bitWidth.Text, true); 890 | opTyped.RegisterIndex = Convert.ToUInt16(ParseRegRef(opCtx.register, cheat).Substring(1), 16); 891 | opTyped.MathType = Enum.Parse(opCtx.func.Text, true); 892 | opTyped.Value = Convert.ToUInt32(ParseNumRef(opCtx.value), 16); 893 | CheckValueFitsBitWidth(opTyped.BitWidth, opTyped.Value); 894 | cheat.Opcodes.Add(opTyped); 895 | } 896 | 897 | private void AssembleOpCode5(OpCode5Context opCtx, Cheat cheat) 898 | { 899 | Opcode5LoadRegWithMem opTyped = new Opcode5LoadRegWithMem(); 900 | 901 | opTyped.BitWidth = Enum.Parse(opCtx.bitWidth.Text, true); 902 | 903 | opTyped.RegisterIndex = Convert.ToUInt16(ParseRegRef(opCtx.register, cheat).Substring(1), 16); 904 | if (opCtx.numOffset != null) 905 | { 906 | opTyped.Immediate = Convert.ToUInt64(ParseNumRef(opCtx.numOffset), 16); 907 | } else 908 | { 909 | opTyped.Immediate = 0; 910 | } 911 | 912 | if (opCtx.memType != null) 913 | { 914 | opTyped.MemType = Enum.Parse(opCtx.memType.Text, true); 915 | } 916 | else 917 | { 918 | opTyped.UseReg = true; 919 | if (!ParseRegRef(opCtx.register,cheat).Equals(ParseRegRef(opCtx.baseRegister,cheat))) 920 | { 921 | throw new AssemblerException($"On line: {opCtx.Start.Line} instruction: \"{GetCurrentInstructionText(opCtx)}\", Both the destination and source registers must be the same"); 922 | } 923 | } 924 | cheat.Opcodes.Add(opTyped); 925 | } 926 | private string GetCurrentInstructionText(ParserRuleContext ctx) 927 | { 928 | return parsingContent.Substring(ctx.Start.StartIndex, ctx.stop.StopIndex - ctx.start.StartIndex + 1); 929 | } 930 | private void AssembleOpCode4(OpCode4Context opCtx, Cheat cheat) 931 | { 932 | Opcode4LoadRegWithStatic opTyped = new Opcode4LoadRegWithStatic(); 933 | 934 | 935 | opTyped.RegisterIndex = Convert.ToUInt16(ParseRegRef(opCtx.register, cheat).Substring(1), 16); 936 | opTyped.Value = Convert.ToUInt64(ParseNumRef(opCtx.value), 16); 937 | 938 | /* not strictly needed by Atmosphere, as the bitwidth isn't encoded... but if they provided it we should check the literal fits */ 939 | if (opCtx.bitWidth != null) 940 | { 941 | var bitWidth = Enum.Parse(opCtx.bitWidth.Text, true); 942 | CheckValueFitsBitWidth(bitWidth, opTyped.Value); 943 | } 944 | 945 | cheat.Opcodes.Add(opTyped); 946 | } 947 | 948 | private void AssembleOpCode3(OpCode3Context opCtx, Cheat cheat) 949 | { 950 | Opcode3Loop opTyped = new Opcode3Loop(); 951 | 952 | if (opCtx.endloop != null) 953 | { 954 | opTyped.IsEnd = true; 955 | opTyped.RegisterIndex = Convert.ToUInt16(ParseRegRef(opCtx.register, cheat).Substring(1), 16); 956 | } 957 | else 958 | { 959 | opTyped.IsEnd = false; 960 | opTyped.RegisterIndex = Convert.ToUInt16(ParseRegRef(opCtx.register, cheat).Substring(1), 16); 961 | opTyped.Count = Convert.ToUInt32(ParseNumRef(opCtx.value), 16); 962 | } 963 | cheat.Opcodes.Add(opTyped); 964 | } 965 | 966 | private void AssembleOpCode2(OpCode2Context opCtx, Cheat cheat) 967 | { 968 | var endCond = new Opcode2EndConditional(); 969 | endCond.IsElse = (opCtx.ELSE() != null); 970 | cheat.Opcodes.Add(endCond); 971 | } 972 | 973 | private void AssembleOpCode1(OpCode1Context opCtx, Cheat cheat) 974 | { 975 | Opcode1Conditional opTyped = new Opcode1Conditional(); 976 | 977 | opTyped.BitWidth = Enum.Parse(opCtx.bitWidth.Text, true); 978 | opTyped.MemType = Enum.Parse(opCtx.memType.Text, true); 979 | 980 | opTyped.Condition = Enum.Parse(opCtx.cond.Text, true); 981 | opTyped.Immediate = Convert.ToUInt64(ParseNumRef(opCtx.offset), 16); 982 | opTyped.Value = Convert.ToUInt64(ParseNumRef(opCtx.value), 16); 983 | 984 | CheckValueFitsBitWidth(opTyped.BitWidth, opTyped.Value); 985 | 986 | cheat.Opcodes.Add(opTyped); 987 | } 988 | 989 | private void AssembleOpCode0(OpCode0Context opCtx, Cheat cheat) 990 | { 991 | Opcode0StoreStaticToMemory opTyped = new(); 992 | 993 | opTyped.BitWidth = Enum.Parse(opCtx.bitWidth.Text, true); 994 | opTyped.MemType = Enum.Parse(opCtx.memType.Text, true); 995 | 996 | opTyped.OffsetRegister = Convert.ToUInt16(ParseRegRef(opCtx.regOffset, cheat).Substring(1), 16); 997 | 998 | if (opCtx.numOffset != null) 999 | { 1000 | opTyped.RelativeOffset = Convert.ToUInt64(ParseNumRef(opCtx.numOffset), 16); 1001 | } 1002 | 1003 | opTyped.Value = Convert.ToUInt64(ParseNumRef(opCtx.value), 16); 1004 | CheckValueFitsBitWidth(opTyped.BitWidth, opTyped.Value); 1005 | cheat.Opcodes.Add(opTyped); 1006 | } 1007 | private static void CheckValueFitsBitWidth(BitWidthType width, ulong value) 1008 | { 1009 | bool valueFits = true; 1010 | ulong valueMax = 0; 1011 | switch (width) 1012 | { 1013 | case BitWidthType.b: 1014 | valueMax = byte.MaxValue; 1015 | break; 1016 | case BitWidthType.w: 1017 | valueMax = ushort.MaxValue; 1018 | break; 1019 | case BitWidthType.d: 1020 | valueMax = uint.MaxValue; 1021 | break; 1022 | case BitWidthType.q: 1023 | valueMax = ulong.MaxValue; 1024 | break; 1025 | } 1026 | 1027 | if (value > valueMax) 1028 | { 1029 | throw new AssemblerException($"Instruction has bit width: '{width}' but value 0x{value:X} exceeds the maximum of 0x{valueMax:X}"); 1030 | } 1031 | } 1032 | 1033 | public void AssembleStatement(StatementContext stmt,Cheat cheat) 1034 | { 1035 | switch(stmt.GetRuleContext(0)) 1036 | { 1037 | case OpCode0Context op: 1038 | AssembleOpCode0(op, cheat); 1039 | break; 1040 | case OpCode1Context op: 1041 | AssembleOpCode1(op, cheat); 1042 | break; 1043 | case OpCode2Context op: 1044 | AssembleOpCode2(op, cheat); 1045 | break; 1046 | case OpCode3Context op: 1047 | AssembleOpCode3(op, cheat); 1048 | break; 1049 | case OpCode4Context op: 1050 | AssembleOpCode4(op, cheat); 1051 | break; 1052 | case OpCode5Context op: 1053 | AssembleOpCode5(op, cheat); 1054 | break; 1055 | case OpCode6Context op: 1056 | AssembleOpCode6(op, cheat); 1057 | break; 1058 | case OpCode7Context op: 1059 | AssembleOpCode7(op, cheat); 1060 | break; 1061 | case OpCode8Context op: 1062 | AssembleOpCode8(op, cheat); 1063 | break; 1064 | case OpCode9Context op: 1065 | AssembleOpCode9(op, cheat); 1066 | break; 1067 | case OpCodeAContext op: 1068 | AssembleOpCodeA(op, cheat); 1069 | break; 1070 | case OpCodeC0Context op: 1071 | AssembleOpCodeC0(op, cheat); 1072 | break; 1073 | case OpCodeC1C2Context op: 1074 | AssembleOpCodeC1C2(op, cheat); 1075 | break; 1076 | case OpCodeC3Context op: 1077 | AssembleOpCodeC3(op, cheat); 1078 | break; 1079 | case OpCodeFF0Context op: 1080 | AssembleOpCodeFF0(op, cheat); 1081 | break; 1082 | case OpCodeFF1Context op: 1083 | AssembleOpCodeFF1(op, cheat); 1084 | break; 1085 | case OpCodeFFFContext op: 1086 | AssembleOpCodeFFF(op, cheat); 1087 | break; 1088 | case IfStatementContext op: 1089 | AssembleIfStatement(op, cheat); 1090 | break; 1091 | } 1092 | } 1093 | 1094 | private void AssembleIfStatement(IfStatementContext opCtx, Cheat cheat) 1095 | { 1096 | var compareBitWidth = Enum.Parse(opCtx.bitWidth.Text, true); 1097 | bool containsElse = opCtx.IF_ELSE() != null; 1098 | 1099 | IfStatementType ifType; 1100 | 1101 | if (opCtx.leftMemType != null) 1102 | { 1103 | ifType = IfStatementType.CODE1; 1104 | } else 1105 | { 1106 | if (opCtx.memType != null) 1107 | { 1108 | ifType = IfStatementType.CODEC0MEM; 1109 | } else 1110 | { 1111 | if (opCtx.addrReg != null) 1112 | { 1113 | ifType = IfStatementType.CODEC0REG; 1114 | } else 1115 | { 1116 | ifType = IfStatementType.CODEC0VAL; 1117 | } 1118 | } 1119 | } 1120 | 1121 | CheatOpcode conditionalOp = null; 1122 | CheatOpcode elseCondition = new Opcode2EndConditional() { IsElse = true }; 1123 | if (ifType == IfStatementType.CODE1) 1124 | { 1125 | conditionalOp = new Opcode1Conditional(); 1126 | var typed = (Opcode1Conditional)conditionalOp; 1127 | typed.BitWidth = compareBitWidth; 1128 | typed.MemType = Enum.Parse(opCtx.leftMemType.Text); 1129 | typed.Immediate = Convert.ToUInt64(ParseNumRef(opCtx.numOffset), 16); 1130 | typed.Value = Convert.ToUInt64(ParseNumRef(opCtx.value), 16); 1131 | typed.Condition = CompareOperatorMap[opCtx.CONDITIONAL_SYMBOL().ToString()]; 1132 | } 1133 | else 1134 | { 1135 | /* we'll be using C0 for the conditional check */ 1136 | conditionalOp = new OpcodeC0RegisterConditional(); 1137 | var typed = (OpcodeC0RegisterConditional)conditionalOp; 1138 | typed.BitWidth = compareBitWidth; 1139 | typed.Condition = CompareOperatorMap[opCtx.CONDITIONAL_SYMBOL().ToString()]; 1140 | typed.SourceRegister = Convert.ToUInt16(ParseRegRef(opCtx.reg, cheat).Substring(1), 16); 1141 | if (ifType == IfStatementType.CODEC0VAL) 1142 | { 1143 | /* operand type is either 2 or 3 */ 1144 | if (GetAnyRefType(opCtx.rightAny) == AnyRefType.NUMBER) 1145 | { 1146 | typed.Value = Convert.ToUInt64(ParseAnyRef(opCtx.rightAny, AnyRefType.NUMBER, cheat), 16); 1147 | CheckValueFitsBitWidth(typed.BitWidth, typed.Value); 1148 | typed.OperandType = 4; 1149 | } 1150 | else if (GetAnyRefType(opCtx.rightAny) == AnyRefType.REGISTER) 1151 | { 1152 | typed.OtherRegister = Convert.ToUInt16(ParseAnyRef(opCtx.rightAny, AnyRefType.REGISTER, cheat).Substring(1), 16); 1153 | typed.OperandType = 5; 1154 | } 1155 | } 1156 | else if (ifType == IfStatementType.CODEC0REG) 1157 | { 1158 | typed.AddressRegister = Convert.ToUInt32(ParseRegRef(opCtx.addrReg, cheat).Substring(1), 16); 1159 | /* operand type is either 2 or 3 */ 1160 | if (opCtx.anyOffset == null || GetAnyRefType(opCtx.anyOffset) == AnyRefType.NUMBER) 1161 | { 1162 | if (opCtx.anyOffset != null) 1163 | { 1164 | typed.RelativeAddress = Convert.ToUInt64(ParseAnyRef(opCtx.anyOffset, AnyRefType.NUMBER, cheat), 16); 1165 | } 1166 | else 1167 | { 1168 | typed.RelativeAddress = 0; 1169 | } 1170 | typed.OperandType = 2; 1171 | } 1172 | else if (GetAnyRefType(opCtx.anyOffset) == AnyRefType.REGISTER) 1173 | { 1174 | typed.OffsetRegister = Convert.ToUInt16(ParseAnyRef(opCtx.anyOffset, AnyRefType.REGISTER, cheat).Substring(1), 16); 1175 | typed.OperandType = 3; 1176 | } 1177 | } else if (ifType == IfStatementType.CODEC0MEM) 1178 | { 1179 | typed.MemType = Enum.Parse(opCtx.memType.Text, true); 1180 | 1181 | /* operand type is either 0 or 1... */ 1182 | if (opCtx.anyOffset == null || GetAnyRefType(opCtx.anyOffset) == AnyRefType.NUMBER) 1183 | { 1184 | if (opCtx.anyOffset != null) 1185 | { 1186 | typed.RelativeAddress = Convert.ToUInt64(ParseAnyRef(opCtx.anyOffset, AnyRefType.NUMBER, cheat), 16); 1187 | } 1188 | else 1189 | { 1190 | typed.RelativeAddress = 0; 1191 | } 1192 | typed.OperandType = 0; 1193 | } 1194 | else if (GetAnyRefType(opCtx.anyOffset) == AnyRefType.REGISTER) 1195 | { 1196 | typed.OffsetRegister = Convert.ToUInt16(ParseAnyRef(opCtx.anyOffset, AnyRefType.REGISTER, cheat).Substring(1), 16); 1197 | typed.OperandType = 1; 1198 | } 1199 | } 1200 | } 1201 | 1202 | cheat.Opcodes.Add(conditionalOp); 1203 | 1204 | foreach(var stmt in opCtx.stmtList.statement()) 1205 | { 1206 | AssembleStatement(stmt, cheat); 1207 | } 1208 | 1209 | if (containsElse) 1210 | { 1211 | cheat.Opcodes.Add(elseCondition); 1212 | foreach (var stmt in opCtx.elseStmtList.statement()) 1213 | { 1214 | AssembleStatement(stmt, cheat); 1215 | } 1216 | } 1217 | 1218 | cheat.Opcodes.Add(new Opcode2EndConditional()); 1219 | 1220 | } 1221 | } 1222 | 1223 | 1224 | 1225 | 1226 | enum IfStatementType 1227 | { 1228 | CODE1, CODEC0MEM, CODEC0REG, CODEC0VAL 1229 | } 1230 | } 1231 | -------------------------------------------------------------------------------- /CheatASM/CheatASM.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | True 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /CheatASM/CheatASM.g4: -------------------------------------------------------------------------------- 1 | grammar CheatASM; 2 | 3 | program: gameInfo? titleID? buildID? variableDecl* cheatEntry+ 4 | | variableDecl* statementList; 5 | 6 | statement: opCode1 7 | | opCode2 8 | | opCode3 9 | | opCode7 10 | | opCode8 11 | | opCode9 12 | | movInstr 13 | | opCodeC0 14 | | opCodeC1C2 15 | | opCodeC3 16 | | opCodeFF0 17 | | opCodeFF1 18 | | opCodeFFF 19 | | ifStatement 20 | | COMMENT_LINE; 21 | 22 | statementList: (stmt=statement+); 23 | 24 | cheatEntry: (header=cheatHeader) statementList; 25 | 26 | titleID: DOT 'title' (title=ID); 27 | buildID: DOT 'build' (build=ID); 28 | gameInfo: DOT 'gameinfo' (info=CHEAT_NAME); 29 | 30 | cheatHeader: DOT 'cheat' (master=MASTER)? (name=CHEAT_NAME); 31 | 32 | /* cond.d [HEAP + num], num */ 33 | opCode1: (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) LSQUARE(memType=MEM_TYPE)PLUS_SIGN(offset=numRef)RSQUARE COMMA (value=numRef); 34 | opCode2: END_COND 35 | | ELSE; 36 | opCode3: LOOP (register=regRef) COMMA(value=numRef) 37 | | (endloop=END_LOOP) (register=regRef); 38 | 39 | opCode7: (func=LEGACY_ARITHMETIC) DOT (bitWidth=BIT_WIDTH) (register=regRef) COMMA (value=numRef); 40 | 41 | /* lexing hack to get B to work, assembler will complain if W/D/Q are used */ 42 | opCode8: KEYCHECK (key=(BIT_WIDTH|KEY)); 43 | 44 | opCode9: (func=LEGACY_ARITHMETIC) DOT (bitWidth=BIT_WIDTH) (dest=regRef) COMMA (leftReg=regRef) COMMA (right=anyRef) 45 | | (func=ARITHMETIC) DOT (bitWidth=BIT_WIDTH) (dest=regRef) COMMA (leftReg=regRef) COMMA (right=anyRef) 46 | | (func=NO_RIGHT_HAND_ARITH) DOT (bitWidth=BIT_WIDTH) (dest=regRef) COMMA (leftReg=regRef); 47 | 48 | movInstr: MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (memType=MEM_TYPE) PLUS_SIGN (regOffset=regRef) (PLUS_SIGN (numOffset=numRef))? RSQUARE COMMA (value=numRef) #opCode0 49 | | MOVE (DOT (bitWidth=BIT_WIDTH))? (register=regRef) COMMA (value=numRef) # opCode4 50 | | MOVE DOT (bitWidth=BIT_WIDTH) (register=regRef) COMMA LSQUARE (memType=MEM_TYPE) (PLUS_SIGN (numOffset=numRef))? RSQUARE # opCode5 51 | | MOVE DOT (bitWidth=BIT_WIDTH) (register=regRef) COMMA LSQUARE (baseRegister=regRef) (PLUS_SIGN (numOffset=numRef))? RSQUARE # opCode5 52 | | MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (base=regRef) (PLUS_SIGN (regOffset=regRef))? RSQUARE COMMA (value=numRef) (increment=INCREMENT)? #opCode6 53 | | MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (base=regRef) (PLUS_SIGN (regOffset=regRef))? RSQUARE COMMA (regValue=regRef) (increment=INCREMENT)? #opCodeA 54 | | MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (base=regRef) (PLUS_SIGN (numOffset=numRef))? RSQUARE COMMA (regValue=regRef) (increment=INCREMENT)? #opCodeA 55 | | MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (memType=MEM_TYPE) (PLUS_SIGN (regOffset=regRef))? (PLUS_SIGN (numOffset=numRef))? RSQUARE COMMA (regValue=regRef) #opCodeA; 56 | 57 | opCodeC0: (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) (source=regRef) COMMA LSQUARE (memType=MEM_TYPE) (PLUS_SIGN (offset=anyRef))? RSQUARE 58 | | (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) (source=regRef) COMMA LSQUARE (addrReg=regRef) (PLUS_SIGN (offset=anyRef))? RSQUARE 59 | | (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) (source=regRef) COMMA (value=anyRef); 60 | 61 | opCodeC1C2: (func=SAVE) DOT (type=REG) (index=numRef) COMMA (reg=regRef) 62 | | (func=LOAD) DOT (type=REG) (reg=regRef) COMMA (index=numRef) 63 | | (func=SAVE) DOT (type=REGS) (regs=regList) 64 | | (func=LOAD) DOT (type=REGS) (regs=regList) 65 | | (func=CLEAR) DOT (type=REG) (reg=regRef) 66 | | (func=CLEAR) DOT (type=SAVED) (index=numRef) 67 | | (func=CLEAR) DOT (type=REGS) (regs=regList) 68 | | (func=CLEAR) DOT (type=SAVED) (indexes=indexList); 69 | 70 | opCodeC3: (func=SAVE) DOT (type=STATIC) (sreg=SREGISTER) COMMA (reg=regRef) 71 | | (func=LOAD) DOT (type=STATIC) (reg=regRef) COMMA (sreg=SREGISTER); 72 | 73 | opCodeFF0: (func=PAUSE); 74 | 75 | opCodeFF1: (func=RESUME); 76 | 77 | opCodeFFF: (func=LOG) DOT (bitWidth=BIT_WIDTH) (id=HEX_NUMBER) COMMA LSQUARE (memType=MEM_TYPE) (PLUS_SIGN (offset=anyRef))? RSQUARE 78 | | (func=LOG) DOT (bitWidth=BIT_WIDTH) (id=HEX_NUMBER) COMMA LSQUARE (addrReg=regRef) (PLUS_SIGN (offset=anyRef))? RSQUARE 79 | | (func=LOG) DOT (bitWidth=BIT_WIDTH) (id=HEX_NUMBER) COMMA (value=regRef); 80 | 81 | numberLiteral : IntegerLiteral | DecimalLiteral | HEX_NUMBER; 82 | regRef: (reg=REGISTER) | (var=VARIABLE_NAME); 83 | 84 | numRef: (num=HEX_NUMBER) | (var=VARIABLE_NAME); 85 | 86 | anyRef: (reg=REGISTER) | (num=HEX_NUMBER) | (var=VARIABLE_NAME); 87 | 88 | variableDecl: (name=VARIABLE_NAME) COLON (type=VARIABLE_TYPE) (const=CONST)? (val=numberLiteral); 89 | 90 | regList: (reg=regRef) (COMMA regRef)*; 91 | indexList: (index=numRef) (COMMA numRef)*; 92 | 93 | ifStatement: IF DOT (bitWidth=BIT_WIDTH) (reg=regRef) (cond=CONDITIONAL_SYMBOL) (rightAny=anyRef) (stmtList=statementList) (IF_ELSE (elseStmtList=statementList))? FI 94 | | IF DOT (bitWidth=BIT_WIDTH) (reg=regRef) (cond=CONDITIONAL_SYMBOL) LSQUARE (memType=MEM_TYPE) (PLUS_SIGN (anyOffset=anyRef))? RSQUARE (stmtList=statementList) (IF_ELSE (elseStmtList=statementList))? FI 95 | | IF DOT (bitWidth=BIT_WIDTH) (reg=regRef) (cond=CONDITIONAL_SYMBOL) LSQUARE (addrReg=regRef) (PLUS_SIGN (anyOffset=anyRef))? RSQUARE (stmtList=statementList) (IF_ELSE (elseStmtList=statementList))? FI 96 | | IF DOT (bitWidth=BIT_WIDTH) LSQUARE(leftMemType=MEM_TYPE)PLUS_SIGN(numOffset=numRef)RSQUARE CONDITIONAL_SYMBOL (value=numRef) (stmtList=statementList) (IF_ELSE (elseStmtList=statementList))? FI; 97 | 98 | // Lexer Rules 99 | 100 | MOVE: M O V; 101 | CONDITIONAL: GT | GE | LT | LE | EQ | NE; 102 | CONDITIONAL_SYMBOL: '>' | '>=' | '<' | '<=' | '==' | '!='; 103 | LOOP: L O O P; 104 | END_LOOP: E N D L O O P; 105 | KEYCHECK: K E Y C H E C K; 106 | ELSE: E L S E; 107 | END_COND: E N D C O N D; 108 | INCREMENT: I N C; 109 | SAVE: S A V E ; 110 | SAVEREG: S A V E R E G; 111 | SAVEALL: S A V E A L L; 112 | LOAD: L O A D; 113 | LOADREG: L O A D R E G; 114 | LOADALL: L O A D A L L; 115 | CLEAR: C L E A R; 116 | REG: R E G; 117 | REGS: R E G S; 118 | SAVED: S A V E D; 119 | PAUSE: P A U S E; 120 | RESUME: R E S U M E; 121 | STATIC: S T A T I C; 122 | LOG: L O G; 123 | LSQUARE: '['; 124 | RSQUARE: ']'; 125 | LCURL: '{'; 126 | RCURL: '}'; 127 | PLUS_SIGN: '+'; 128 | COMMA: ','; 129 | // memory types 130 | IF :DOT I F; 131 | FI : DOT F I; 132 | IF_ELSE: DOT E L S E; 133 | 134 | MEM_TYPE: MEMORY_HEAP | MEMORY_MAIN | MEMORY_ALIAS | MEMORY_ASLR; 135 | MEMORY_MAIN: M A I N; 136 | MEMORY_HEAP: H E A P; 137 | MEMORY_ALIAS: A L I A S; 138 | MEMORY_ASLR: A S L R; 139 | 140 | BIT_WIDTH: BIT_BYTE | BIT_WORD | BIT_DOUBLE | BIT_QUAD; 141 | BIT_BYTE: B; 142 | BIT_WORD: W; 143 | BIT_DOUBLE: D; 144 | BIT_QUAD: Q; 145 | 146 | 147 | // comparisons 148 | GT: G T; 149 | GE: G E; 150 | LT: L T; 151 | LE: L E; 152 | EQ: E Q; 153 | NE: N E; 154 | 155 | // arithmetic 156 | LEGACY_ARITHMETIC: ADD | SUB | MUL | LSH | RSH ; 157 | ARITHMETIC: AND | OR | XOR ; 158 | NO_RIGHT_HAND_ARITH: NOT | COPY; 159 | 160 | ADD: A D D; 161 | SUB: S U B; 162 | MUL: M U L; 163 | LSH: L S H; 164 | RSH: R S H; 165 | AND: A N D; 166 | OR: O R; 167 | NOT: N O T; 168 | XOR: X O R; 169 | NONE: N O N E; 170 | COPY: C O P Y; 171 | 172 | // keys 173 | KEY: A_KEY | B_KEY | X_KEY | Y_KEY | LSP_KEY | RSP_KEY | L_KEY 174 | | R_KEY | ZL_KEY | ZR_KEY | PLUS_KEY | MINUS_KEY | LEFT_KEY 175 | | UP_KEY | RIGHT_KEY | DOWN_KEY | LSL_KEY | LSU_KEY | LSR_KEY 176 | | LSD_KEY | RSL_KEY | RSU_KEY | RSR_KEY | RSD_KEY | SL_KEY | SR_KEY; 177 | A_KEY: A; 178 | B_KEY: B; 179 | X_KEY: X; 180 | Y_KEY: Y; 181 | LSP_KEY: L S P; 182 | RSP_KEY: R S P; 183 | L_KEY: L; 184 | R_KEY: R; 185 | ZL_KEY: Z L; 186 | ZR_KEY: Z R; 187 | PLUS_KEY: P L U S; 188 | MINUS_KEY: M I N U S; 189 | LEFT_KEY: L E F T; 190 | UP_KEY: U P; 191 | RIGHT_KEY: R I G H T; 192 | DOWN_KEY: D O W N; 193 | LSL_KEY: L S L; 194 | LSU_KEY: L S U; 195 | LSR_KEY: L S R; 196 | LSD_KEY: L S D; 197 | RSL_KEY: R S L; 198 | RSU_KEY: R S U; 199 | RSR_KEY: R S R; 200 | RSD_KEY: R S D; 201 | SL_KEY: S L; 202 | SR_KEY: S R; 203 | 204 | CONST: C O N S T; 205 | 206 | MASTER: M A S T E R; 207 | 208 | REGISTER: [Rr][0-9a-fA-F]; 209 | SREGISTER: [Ss][Rr][0-9a-fA-F]+; 210 | HEX_NUMBER: '0' X [0-9a-fA-F]+; 211 | WS: [ \t\r\n]+ -> skip; 212 | DOT: '.'; 213 | COLON: ':'; 214 | VARIABLE_NAME: [A-Za-z][A-Za-z0-9]*; 215 | DecimalLiteral : IntegerLiteral '.' [0-9]+ ; 216 | IntegerLiteral : '0' | '1'..'9' '0'..'9'* ; 217 | VARIABLE_TYPE: DOT ((U | S) ('8' | '16' | '32' | '64') | F ('32' | '64')); 218 | 219 | ID: '{'[0-9A-Fa-f]+ '}' ; 220 | 221 | CHEAT_NAME: '"' [0-9A-Za-z \\.()_]+ '"'; 222 | 223 | COMMENT_LINE: '#' ~[\n\r]* -> skip; 224 | 225 | fragment A : [aA]; // match either an 'a' or 'A' 226 | fragment B : [bB]; 227 | fragment C : [cC]; 228 | fragment D : [dD]; 229 | fragment E : [eE]; 230 | fragment F : [fF]; 231 | fragment G : [gG]; 232 | fragment H : [hH]; 233 | fragment I : [iI]; 234 | fragment J : [jJ]; 235 | fragment K : [kK]; 236 | fragment L : [lL]; 237 | fragment M : [mM]; 238 | fragment N : [nN]; 239 | fragment O : [oO]; 240 | fragment P : [pP]; 241 | fragment Q : [qQ]; 242 | fragment R : [rR]; 243 | fragment S : [sS]; 244 | fragment T : [tT]; 245 | fragment U : [uU]; 246 | fragment V : [vV]; 247 | fragment W : [wW]; 248 | fragment X : [xX]; 249 | fragment Y : [yY]; 250 | fragment Z : [zZ]; -------------------------------------------------------------------------------- /CheatASM/CheatTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | /* The types located in this file are based of the Atmosphère cheat implementation: 6 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 7 | */ 8 | 9 | namespace CheatASM 10 | { 11 | public enum CheatOpcodeType : uint 12 | { 13 | StoreStatic = 0, 14 | BeginConditionalBlock = 1, 15 | EndConditionalBlock = 2, 16 | ControlLoop = 3, 17 | LoadRegisterStatic = 4, 18 | LoadRegisterMemory = 5, 19 | StoreStaticToAddress = 6, 20 | PerformArithmeticStatic = 7, 21 | BeginKeypressConditionalBlock = 8, 22 | PerformArithmeticRegister = 9, 23 | StoreRegisterToAddress = 10, 24 | ExtendedWidth = 12, 25 | } 26 | 27 | public enum BitWidthType : uint 28 | { 29 | b = 1, 30 | w = 2, 31 | d = 4, 32 | q = 8 33 | } 34 | public enum MemoryAccessType : uint 35 | { 36 | MAIN = 0, 37 | HEAP = 1, 38 | ALIAS = 2, 39 | ASLR = 3 40 | }; 41 | 42 | public enum ConditionalComparisonType : uint 43 | { 44 | gt = 1, 45 | ge = 2, 46 | lt = 3, 47 | le = 4, 48 | eq = 5, 49 | ne = 6, 50 | }; 51 | 52 | public enum RegisterArithmeticType : uint 53 | { 54 | add = 0, 55 | sub = 1, 56 | mul = 2, 57 | lsh = 3, 58 | rsh = 4, 59 | and = 5, 60 | or = 6, 61 | not = 7, 62 | xor = 8, 63 | 64 | copy = 9, 65 | }; 66 | 67 | public enum StoreRegisterOffsetType : uint 68 | { 69 | None = 0, 70 | Reg = 1, 71 | Imm = 2, 72 | }; 73 | 74 | public enum KeyMask: uint 75 | { 76 | A = 0x1, 77 | B = 0x2, 78 | X = 0x4, 79 | Y = 0x8, 80 | LSP = 0x10, 81 | RSP = 0x20, 82 | L = 0x40, 83 | R = 0x80, 84 | ZL = 0x100, 85 | ZR = 0x200, 86 | PLUS = 0x400, 87 | MINUS = 0x800, 88 | LEFT = 0x1000, 89 | UP = 0x2000, 90 | RIGHT = 0x4000, 91 | DOWN = 0x8000, 92 | LSL = 0x10000, 93 | LSU = 0x20000, 94 | LSR = 0x40000, 95 | LSD = 0x80000, 96 | RSL = 0x100000, 97 | RSU = 0x200000, 98 | RSR = 0x400000, 99 | RSD = 0x800000, 100 | SL = 0x1000000, 101 | SR = 0x2000000 102 | } 103 | 104 | public abstract class CheatOpcode 105 | { 106 | public CheatOpcode Clone() 107 | { 108 | return (CheatOpcode)this.MemberwiseClone(); 109 | } 110 | protected static uint GetNibble(UInt32 block, uint index) 111 | { 112 | return (block >> (int)(32 - (index * 4))) & 0xF; 113 | } 114 | 115 | protected static void SetNibble(ref uint block, uint index, uint value) 116 | { 117 | uint byteMask = (uint)0xFFFFFFFF - (uint)(0xF << (int)(32 - (index * 4))); 118 | block &= byteMask; 119 | 120 | value &= 0xF; 121 | value <<= (int)(32 - (index * 4)); 122 | block |= value; 123 | } 124 | 125 | protected static string GetBlocksAsString(uint[] blocks) 126 | { 127 | StringBuilder sb = new StringBuilder(); 128 | for (var x = 0; x < blocks.Length; x++) 129 | { 130 | sb.Append(blocks[x].ToString("X8")).Append(" "); 131 | } 132 | 133 | return sb.ToString().Trim(); 134 | } 135 | 136 | public abstract string ToASM(); 137 | public abstract string ToByteString(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /CheatASM/Disassembler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Globalization; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace CheatASM 10 | { 11 | public class Disassembler 12 | { 13 | uint conditionalIndent = 0; 14 | 15 | public void ResetIndent() 16 | { 17 | conditionalIndent = 0; 18 | } 19 | 20 | private void WriteOpcode(CheatOpcode op, StringBuilder sb) 21 | { 22 | var indent = string.Concat(Enumerable.Repeat(" ", (int)conditionalIndent)); 23 | sb.Append(indent); 24 | sb.Append(op.ToASM()); 25 | } 26 | 27 | public string DisassembleString(string contents) 28 | { 29 | StringBuilder sb = new StringBuilder(); 30 | foreach(var line in contents.Split('\n',StringSplitOptions.RemoveEmptyEntries)) 31 | { 32 | sb.AppendLine(DisassembleLine(line)); 33 | } 34 | 35 | return sb.ToString(); 36 | } 37 | public string DisassembleLine(string line) 38 | { 39 | /* trim the line... */ 40 | line = line.Trim(); 41 | StringBuilder sb = new StringBuilder(); 42 | CheatOpcode op = null; 43 | /* its a code... lets parse it */ 44 | try 45 | { 46 | uint[] blocks = line.Split(' ').Select(s => uint.Parse(s, NumberStyles.HexNumber)).ToArray(); 47 | 48 | switch (line[0]) 49 | { 50 | case '0': 51 | op = new Opcode0StoreStaticToMemory(blocks); 52 | WriteOpcode(op, sb); 53 | break; 54 | case '1': 55 | op = new Opcode1Conditional(blocks); 56 | WriteOpcode(op, sb); 57 | conditionalIndent++; 58 | break; 59 | case '2': 60 | op = new Opcode2EndConditional(blocks); 61 | if (conditionalIndent > 0) 62 | { 63 | conditionalIndent--; 64 | } 65 | WriteOpcode(op, sb); 66 | if (((Opcode2EndConditional)op).IsElse) 67 | { 68 | conditionalIndent++; 69 | } 70 | break; 71 | case '3': 72 | op = new Opcode3Loop(blocks); 73 | if (((Opcode3Loop)op).IsEnd) 74 | { 75 | if (conditionalIndent > 0) 76 | { 77 | conditionalIndent--; 78 | } 79 | } 80 | WriteOpcode(op, sb); 81 | if (((Opcode3Loop)op).IsEnd == false) 82 | { 83 | conditionalIndent++; 84 | } 85 | break; 86 | case '4': 87 | op = new Opcode4LoadRegWithStatic(blocks); 88 | WriteOpcode(op, sb); 89 | break; 90 | case '5': 91 | /*mov(b/w/d/q) R0 [HEAP+IMM]*/ 92 | op = new Opcode5LoadRegWithMem(blocks); 93 | WriteOpcode(op, sb); 94 | break; 95 | case '6': 96 | op = new Opcode6StoreStaticToAddress(blocks); 97 | WriteOpcode(op, sb); 98 | break; 99 | case '7': 100 | op = new Opcode7LegacyArithmetic(blocks); 101 | WriteOpcode(op, sb); 102 | break; 103 | case '8': 104 | op = new Opcode8KeypressConditional(blocks); 105 | WriteOpcode(op, sb); 106 | conditionalIndent++; 107 | break; 108 | case '9': 109 | op = new Opcode9Arithmetic(blocks); 110 | WriteOpcode(op, sb); 111 | break; 112 | case 'a': 113 | case 'A': 114 | op = new OpcodeAStoreRegToAddress(blocks); 115 | WriteOpcode(op, sb); 116 | break; 117 | case 'C': 118 | /* Extended width set 1 */ 119 | switch(line[1]) 120 | { 121 | case '0': 122 | op = new OpcodeC0RegisterConditional(blocks); 123 | WriteOpcode(op, sb); 124 | conditionalIndent++; 125 | break; 126 | case '1': 127 | op = new OpcodeC1SaveRestoreReg(blocks); 128 | WriteOpcode(op, sb); 129 | break; 130 | case '2': 131 | op = new OpcodeC2SaveRestoreRegMask(blocks); 132 | WriteOpcode(op, sb); 133 | break; 134 | case '3': 135 | op = new OpcodeC3ReadWriteStaticReg(blocks); 136 | WriteOpcode(op, sb); 137 | break; 138 | } 139 | break; 140 | case 'F': 141 | switch(line[1]) 142 | { 143 | case 'F': 144 | switch(line[2]) 145 | { 146 | case '0': 147 | op = new OpcodeFF0PauseProcess(blocks); 148 | WriteOpcode(op, sb); 149 | break; 150 | case '1': 151 | op = new OpcodeFF1ResumeProcess(blocks); 152 | WriteOpcode(op, sb); 153 | break; 154 | case 'F': 155 | op = new OpcodeFFFDebugLog(blocks); 156 | WriteOpcode(op, sb); 157 | break; 158 | } 159 | break; 160 | } 161 | break; 162 | } 163 | } 164 | catch (Exception ex) 165 | { 166 | var indent = string.Concat(Enumerable.Repeat(" ", (int)conditionalIndent)); 167 | sb.Append(indent); 168 | sb.Append("# Invalid Cheat Opcode: " + line.Trim()); 169 | } 170 | return sb.ToString(); 171 | } 172 | public string DisassembleFile(string filePath) 173 | { 174 | StringBuilder sb = new StringBuilder(); 175 | sb.Clear(); 176 | conditionalIndent = 0; 177 | 178 | foreach (string line in File.ReadLines(filePath)) 179 | { 180 | if (line.StartsWith("{") || line.StartsWith("[") || line.Trim().Length == 0) 181 | { 182 | sb.AppendLine(line); 183 | } 184 | else 185 | { 186 | var lineOut = DisassembleLine(line); 187 | sb.AppendLine(lineOut); 188 | 189 | } 190 | } 191 | return sb.ToString(); 192 | } 193 | } 194 | 195 | 196 | } 197 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/Opcode0StoreStaticToMemory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | /* The types located in this file are based of the Atmosphère cheat implementation: 5 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 6 | */ 7 | 8 | namespace CheatASM 9 | { 10 | public class Opcode0StoreStaticToMemory : CheatOpcode 11 | { 12 | public BitWidthType BitWidth; 13 | public MemoryAccessType MemType; 14 | public uint OffsetRegister; 15 | public ulong RelativeOffset; 16 | public UInt64 Value; 17 | 18 | public Opcode0StoreStaticToMemory() { } 19 | 20 | public Opcode0StoreStaticToMemory(UInt32[] blocks) 21 | { 22 | BitWidth = (BitWidthType)GetNibble(blocks[0], 2); 23 | MemType = (MemoryAccessType)GetNibble(blocks[0], 3); 24 | OffsetRegister = GetNibble(blocks[0], 4); 25 | RelativeOffset = blocks[1]; 26 | if (BitWidth == BitWidthType.q) 27 | { 28 | Value = ((UInt64)blocks[2] << 32) | blocks[3]; 29 | } 30 | else 31 | { 32 | Value = blocks[2]; 33 | } 34 | } 35 | 36 | public override string ToASM() 37 | { 38 | StringBuilder sb = new StringBuilder(); 39 | sb.Append("mov"); 40 | sb.Append(".").Append(Enum.GetName(typeof(BitWidthType), BitWidth)); 41 | 42 | sb.Append(" ["); 43 | 44 | sb.Append(Enum.GetName(typeof(MemoryAccessType), MemType)); 45 | 46 | sb.Append(" + R").Append(OffsetRegister.ToString("X")); 47 | if (RelativeOffset > 0) { sb.Append(" + 0x").Append(RelativeOffset.ToString("X")); } 48 | sb.Append("], 0x").Append(Value.ToString("X")); 49 | return sb.ToString(); 50 | } 51 | 52 | public override string ToByteString() 53 | { 54 | /* 0TMR00AA AAAAAAAA VVVVVVVV (VVVVVVVV) */ 55 | uint[] blocks = null; 56 | if (BitWidth == BitWidthType.q) 57 | { 58 | blocks = new uint[4]; 59 | } else 60 | { 61 | blocks = new uint[3]; 62 | } 63 | 64 | /* build first DWORD */ 65 | SetNibble(ref blocks[0], 1, 0); 66 | SetNibble(ref blocks[0], 2, (uint)BitWidth); 67 | SetNibble(ref blocks[0], 3, (uint)MemType); 68 | SetNibble(ref blocks[0], 4, (uint)OffsetRegister); 69 | SetNibble(ref blocks[0], 5, 0); 70 | SetNibble(ref blocks[0], 6, 0); 71 | SetNibble(ref blocks[0], 7, (uint)((RelativeOffset >> 36) & 0xF)); 72 | SetNibble(ref blocks[0], 7, (uint)((RelativeOffset >> 32) & 0xF)); 73 | blocks[1] = (uint)(RelativeOffset & 0xFFFFFFFF); 74 | if (BitWidth == BitWidthType.q) 75 | { 76 | blocks[2] = (uint)(Value >> 32); 77 | blocks[3] = (uint)(Value & 0xFFFFFFFF); 78 | } 79 | else 80 | { 81 | blocks[2] = (uint)(Value & 0xFFFFFFFF); 82 | } 83 | 84 | return GetBlocksAsString(blocks); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/Opcode1Conditional.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | /* The types located in this file are based of the Atmosphère cheat implementation: 5 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 6 | */ 7 | 8 | namespace CheatASM 9 | { 10 | public class Opcode1Conditional : CheatOpcode 11 | { 12 | public BitWidthType BitWidth; 13 | public MemoryAccessType MemType; 14 | public ConditionalComparisonType Condition; 15 | public UInt64 Immediate; 16 | public UInt64 Value; 17 | 18 | public Opcode1Conditional() { } 19 | 20 | public Opcode1Conditional(uint[] blocks) 21 | { 22 | BitWidth = (BitWidthType)GetNibble(blocks[0], 2); 23 | MemType = (MemoryAccessType)GetNibble(blocks[0], 3); 24 | Condition = (ConditionalComparisonType)GetNibble(blocks[0], 4); 25 | Immediate = ((UInt64)(blocks[0] & 0xFF) << 32) + blocks[1]; 26 | 27 | if (BitWidth == BitWidthType.q) 28 | { 29 | Value = ((UInt64)blocks[2] << 32) + blocks[3]; 30 | } 31 | else 32 | { 33 | Value = blocks[2]; 34 | } 35 | } 36 | 37 | public override string ToASM() 38 | { 39 | StringBuilder sb = new StringBuilder(); 40 | sb.Append(Enum.GetName(typeof(ConditionalComparisonType), Condition)); 41 | sb.Append(".").Append(Enum.GetName(typeof(BitWidthType), BitWidth)); 42 | sb.Append(" [").Append(Enum.GetName(typeof(MemoryAccessType), MemType)); 43 | sb.Append(" + 0x").Append(Immediate.ToString("X")); 44 | sb.Append("], 0x").Append(Value.ToString("X")); 45 | 46 | return sb.ToString(); 47 | } 48 | 49 | public override string ToByteString() 50 | { 51 | /* 1TMC00AA AAAAAAAA VVVVVVVV (VVVVVVVV) */ 52 | uint[] blocks = null; 53 | if (BitWidth == BitWidthType.q) 54 | { 55 | blocks = new uint[4]; 56 | } 57 | else 58 | { 59 | blocks = new uint[3]; 60 | } 61 | 62 | /* build first DWORD */ 63 | SetNibble(ref blocks[0], 1, 1); 64 | SetNibble(ref blocks[0], 2, (uint)BitWidth); 65 | SetNibble(ref blocks[0], 3, (uint)MemType); 66 | SetNibble(ref blocks[0], 4, (uint)Condition); 67 | SetNibble(ref blocks[0], 5, 0); 68 | SetNibble(ref blocks[0], 6, 0); 69 | SetNibble(ref blocks[0], 7, (uint)((Immediate >> 36) & 0xF)); 70 | SetNibble(ref blocks[0], 8, (uint)((Immediate >> 32) & 0xF)); 71 | blocks[1] = (uint)(Immediate & 0xFFFFFFFF); 72 | if (BitWidth == BitWidthType.q) 73 | { 74 | blocks[2] = (uint)(Value >> 32); 75 | blocks[3] = (uint)(Value & 0xFFFFFFFF); 76 | } 77 | else 78 | { 79 | blocks[2] = (uint)(Value & 0xFFFFFFFF); 80 | } 81 | 82 | return GetBlocksAsString(blocks); 83 | 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/Opcode2EndConditional.cs: -------------------------------------------------------------------------------- 1 | /* The types located in this file are based of the Atmosphère cheat implementation: 2 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 3 | */ 4 | 5 | namespace CheatASM 6 | { 7 | public class Opcode2EndConditional : CheatOpcode 8 | { 9 | public bool IsElse = false; 10 | public Opcode2EndConditional() { } 11 | 12 | public Opcode2EndConditional(uint[] blocks) 13 | { 14 | IsElse = ((blocks[0]>> 24) & 0xF) == 0x1; 15 | } 16 | 17 | public override string ToASM() 18 | { 19 | if (IsElse) 20 | { 21 | return "else"; 22 | } else 23 | { 24 | return "endcond"; 25 | } 26 | 27 | } 28 | 29 | public override string ToByteString() 30 | { 31 | if (IsElse) 32 | { 33 | return "21000000"; 34 | } 35 | else 36 | { 37 | return "20000000"; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/Opcode3Loop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | /* The types located in this file are based of the Atmosphère cheat implementation: 4 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 5 | */ 6 | 7 | namespace CheatASM 8 | { 9 | public class Opcode3Loop : CheatOpcode 10 | { 11 | public bool IsEnd; 12 | public uint RegisterIndex; 13 | public UInt32 Count; 14 | 15 | public Opcode3Loop() { } 16 | 17 | public Opcode3Loop(uint[] blocks) 18 | { 19 | IsEnd = GetNibble(blocks[0], 2) == 1; 20 | RegisterIndex = GetNibble(blocks[0], 4); 21 | 22 | if (!IsEnd) 23 | { 24 | Count = blocks[1]; 25 | } 26 | } 27 | 28 | public override string ToASM() 29 | { 30 | if (!IsEnd) 31 | { 32 | return "loop R" + RegisterIndex.ToString("X") + ", 0x" + Count.ToString("X"); 33 | } else 34 | { 35 | return "endloop R" + RegisterIndex.ToString("X"); 36 | } 37 | } 38 | 39 | public override string ToByteString() 40 | { 41 | uint[] blocks = null; 42 | if (IsEnd) 43 | { 44 | blocks = new uint[1]; 45 | } else 46 | { 47 | blocks = new uint[2]; 48 | } 49 | 50 | if (IsEnd) 51 | { 52 | SetNibble(ref blocks[0], 1, 3); 53 | SetNibble(ref blocks[0], 2, 1); 54 | SetNibble(ref blocks[0], 3, 0); 55 | SetNibble(ref blocks[0], 4, (uint)RegisterIndex & 0xF); 56 | SetNibble(ref blocks[0], 5, 0); 57 | SetNibble(ref blocks[0], 6, 0); 58 | SetNibble(ref blocks[0], 7, 0); 59 | SetNibble(ref blocks[0], 8, 0); 60 | 61 | } else 62 | { 63 | SetNibble(ref blocks[0], 1, 3); 64 | SetNibble(ref blocks[0], 2, 0); 65 | SetNibble(ref blocks[0], 3, 0); 66 | SetNibble(ref blocks[0], 4, (uint)RegisterIndex & 0xF); 67 | SetNibble(ref blocks[0], 5, 0); 68 | SetNibble(ref blocks[0], 6, 0); 69 | SetNibble(ref blocks[0], 7, 0); 70 | SetNibble(ref blocks[0], 8, 0); 71 | 72 | blocks[1] = Count; 73 | } 74 | 75 | return GetBlocksAsString(blocks); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/Opcode4LoadRegWithStatic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | /* The types located in this file are based of the Atmosphère cheat implementation: 4 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 5 | */ 6 | 7 | namespace CheatASM 8 | { 9 | public class Opcode4LoadRegWithStatic : CheatOpcode 10 | { 11 | public uint RegisterIndex; 12 | public UInt64 Value; 13 | 14 | public Opcode4LoadRegWithStatic() { } 15 | 16 | public Opcode4LoadRegWithStatic(uint[] blocks) 17 | { 18 | RegisterIndex = GetNibble(blocks[0], 4); 19 | Value = ((UInt64)blocks[1] << 32) + blocks[2]; 20 | } 21 | 22 | public override string ToASM() 23 | { 24 | return "mov.q R" + RegisterIndex.ToString("X") + ", 0x" + Value.ToString("X"); 25 | } 26 | 27 | public override string ToByteString() 28 | { 29 | uint[] blocks = new uint[3]; 30 | SetNibble(ref blocks[0], 1, 4); 31 | SetNibble(ref blocks[0], 2, 0); 32 | SetNibble(ref blocks[0], 3, 0); 33 | SetNibble(ref blocks[0], 4, (uint)RegisterIndex & 0xF); 34 | SetNibble(ref blocks[0], 5, 0); 35 | SetNibble(ref blocks[0], 6, 0); 36 | SetNibble(ref blocks[0], 7, 0); 37 | SetNibble(ref blocks[0], 8, 0); 38 | blocks[1] = (uint)((Value >> 32) & 0xFFFFFFFF); 39 | blocks[2] = (uint)(Value & 0xFFFFFFFF); 40 | 41 | return GetBlocksAsString(blocks); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/Opcode5LoadRegWithMem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | /* The types located in this file are based of the Atmosphère cheat implementation: 5 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 6 | */ 7 | 8 | namespace CheatASM 9 | { 10 | public class Opcode5LoadRegWithMem : CheatOpcode 11 | { 12 | public BitWidthType BitWidth; 13 | public MemoryAccessType MemType; 14 | public uint RegisterIndex; 15 | public bool UseReg; 16 | public UInt64 Immediate; 17 | 18 | public Opcode5LoadRegWithMem() { } 19 | 20 | public Opcode5LoadRegWithMem(uint[] blocks) 21 | { 22 | BitWidth = (BitWidthType)GetNibble(blocks[0], 2); 23 | MemType = (MemoryAccessType)GetNibble(blocks[0], 3); 24 | RegisterIndex = GetNibble(blocks[0], 4); 25 | UseReg = GetNibble(blocks[0], 5) == 1; 26 | Immediate = ((UInt64)(blocks[0] & 0xFF) << 32) + blocks[1]; 27 | } 28 | 29 | public override string ToASM() 30 | { 31 | /* mov(b/w/d/q) R0 [HEAP+IMM] */ 32 | /* mov(b/w/d/q) R0 [R0+IMM] */ 33 | StringBuilder sb = new StringBuilder(); 34 | sb.Append("mov"); 35 | sb.Append(".").Append(Enum.GetName(typeof(BitWidthType), BitWidth)); 36 | 37 | sb.Append(" R" + RegisterIndex.ToString("X")).Append(", ["); 38 | if (UseReg) 39 | { 40 | sb.Append("R" + RegisterIndex.ToString("X")); 41 | } 42 | else 43 | { 44 | sb.Append(Enum.GetName(typeof(MemoryAccessType), MemType)); 45 | } 46 | if (Immediate > 0) 47 | { 48 | sb.Append(" + 0x").Append(Immediate.ToString("X")); 49 | } 50 | sb.Append("]"); 51 | return sb.ToString(); 52 | 53 | 54 | } 55 | 56 | public override string ToByteString() 57 | { 58 | /* 5TMR00AA AAAAAAAA */ 59 | uint[] blocks = new uint[2]; 60 | SetNibble(ref blocks[0], 1, 5); 61 | SetNibble(ref blocks[0], 2, ((uint)BitWidth & 0xF)); 62 | SetNibble(ref blocks[0], 3, ((uint)MemType & 0xF)); 63 | SetNibble(ref blocks[0], 4, ((uint)RegisterIndex & 0xF)); 64 | 65 | if (UseReg) 66 | { 67 | SetNibble(ref blocks[0], 5, 1); 68 | } 69 | else 70 | { 71 | SetNibble(ref blocks[0], 5, 0); 72 | } 73 | SetNibble(ref blocks[0], 6, 0); 74 | 75 | SetNibble(ref blocks[0], 7, (uint)((Immediate >> 36) & 0xF)); 76 | SetNibble(ref blocks[0], 8, (uint)((Immediate >> 32) & 0xF)); 77 | 78 | blocks[1] = (uint)(Immediate & 0xFFFFFFFF); 79 | 80 | return GetBlocksAsString(blocks); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/Opcode6StoreStaticToAddress.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | /* The types located in this file are based of the Atmosphère cheat implementation: 5 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 6 | */ 7 | 8 | namespace CheatASM 9 | { 10 | public class Opcode6StoreStaticToAddress : CheatOpcode 11 | { 12 | public BitWidthType BitWidth; 13 | public uint RegisterIndex; 14 | public bool IncrementFlag; 15 | public bool OffsetEnableFlag; 16 | public uint OffsetRegister; 17 | public UInt64 Value; 18 | 19 | public Opcode6StoreStaticToAddress() { } 20 | 21 | public Opcode6StoreStaticToAddress(UInt32[] blocks) 22 | { 23 | BitWidth = (BitWidthType)GetNibble(blocks[0], 2); 24 | RegisterIndex = GetNibble(blocks[0], 4); 25 | IncrementFlag = GetNibble(blocks[0], 5) == 1; 26 | OffsetEnableFlag = GetNibble(blocks[0], 6) == 1; 27 | OffsetRegister = GetNibble(blocks[0], 7); 28 | Value = ((UInt64)blocks[1] << 32) + blocks[2]; 29 | } 30 | 31 | public override string ToASM() 32 | { 33 | /* mov(b/w/d/q) [R0 (+R2)] Value (inc) */ 34 | StringBuilder sb = new StringBuilder(); 35 | 36 | sb.Append("mov.").Append(Enum.GetName(typeof(BitWidthType), BitWidth)); 37 | sb.Append(" [R").Append(RegisterIndex.ToString("X")); 38 | if (OffsetEnableFlag) 39 | { 40 | sb.Append(" + ").Append("R" + OffsetRegister.ToString("X")); 41 | } 42 | sb.Append("], 0x").Append(Value.ToString("X")); 43 | if (IncrementFlag) 44 | { 45 | sb.Append(" inc"); 46 | } 47 | 48 | return sb.ToString(); 49 | } 50 | 51 | public override string ToByteString() 52 | { 53 | /* 6T0RIor0 VVVVVVVV VVVVVVVV */ 54 | 55 | uint[] blocks = new uint[3]; 56 | SetNibble(ref blocks[0], 1, 6); 57 | SetNibble(ref blocks[0], 2, (uint)BitWidth & 0xF); 58 | SetNibble(ref blocks[0], 3, 0); 59 | SetNibble(ref blocks[0], 4, (uint)RegisterIndex & 0xF); 60 | 61 | if (IncrementFlag) 62 | { 63 | SetNibble(ref blocks[0], 5, 1); 64 | } else 65 | { 66 | SetNibble(ref blocks[0], 5, 0); 67 | } 68 | 69 | if (OffsetEnableFlag) 70 | { 71 | SetNibble(ref blocks[0], 6, 1); 72 | } 73 | else 74 | { 75 | SetNibble(ref blocks[0], 6, 0); 76 | } 77 | 78 | SetNibble(ref blocks[0], 7, (uint)OffsetRegister & 0xF); 79 | SetNibble(ref blocks[0], 8, 0); 80 | 81 | blocks[1] = (uint)((Value >> 32) & 0xFFFFFFFF); 82 | blocks[2] = (uint)(Value & 0xFFFFFFFF); 83 | 84 | return GetBlocksAsString(blocks); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/Opcode7LegacyArithmetic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | /* The types located in this file are based of the Atmosphère cheat implementation: 5 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 6 | */ 7 | 8 | namespace CheatASM 9 | { 10 | public class Opcode7LegacyArithmetic : CheatOpcode 11 | { 12 | public BitWidthType BitWidth; 13 | public uint RegisterIndex; 14 | public RegisterArithmeticType MathType; 15 | public uint Value; 16 | 17 | public Opcode7LegacyArithmetic() { } 18 | 19 | public Opcode7LegacyArithmetic(uint[] blocks) 20 | { 21 | BitWidth = (BitWidthType)GetNibble(blocks[0], 2); 22 | RegisterIndex = GetNibble(blocks[0], 4); 23 | MathType = (RegisterArithmeticType)GetNibble(blocks[0], 5); 24 | Value = blocks[1]; 25 | } 26 | 27 | public override string ToASM() 28 | { 29 | StringBuilder sb = new StringBuilder(); 30 | sb.Append(Enum.GetName(typeof(RegisterArithmeticType), MathType)); 31 | sb.Append(".").Append(Enum.GetName(typeof(BitWidthType), BitWidth)); 32 | sb.Append(" R").Append(RegisterIndex.ToString("X")); 33 | sb.Append(", 0x").Append(Value.ToString("X")); 34 | 35 | return sb.ToString(); 36 | } 37 | 38 | public override string ToByteString() 39 | { 40 | /* 7T0RC000 VVVVVVVV */ 41 | uint[] blocks = new uint[2]; 42 | SetNibble(ref blocks[0], 1, 7); 43 | SetNibble(ref blocks[0], 2, ((uint)BitWidth & 0xF)); 44 | SetNibble(ref blocks[0], 3, 0); 45 | SetNibble(ref blocks[0], 4, ((uint)RegisterIndex & 0xF)); 46 | SetNibble(ref blocks[0], 5, ((uint)MathType & 0xF)); 47 | SetNibble(ref blocks[0], 6, 0); 48 | SetNibble(ref blocks[0], 7, 0); 49 | SetNibble(ref blocks[0], 8, 0); 50 | 51 | blocks[1] = (uint)Value; 52 | 53 | return GetBlocksAsString(blocks); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/Opcode8KeypressConditional.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | /* The types located in this file are based of the Atmosphère cheat implementation: 4 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 5 | */ 6 | 7 | namespace CheatASM 8 | { 9 | public class Opcode8KeypressConditional : CheatOpcode 10 | { 11 | public KeyMask Mask; 12 | public Opcode8KeypressConditional() { } 13 | public Opcode8KeypressConditional(UInt32[] blocks) 14 | { 15 | Mask = (KeyMask)(blocks[0] & (0xFFFFFFF)); 16 | } 17 | 18 | public override string ToASM() 19 | { 20 | return "keycheck " + Enum.GetName(typeof(KeyMask),Mask); 21 | } 22 | 23 | public override string ToByteString() 24 | { 25 | uint[] blocks = new uint[1]; 26 | SetNibble(ref blocks[0], 1, 8); 27 | 28 | var byteMask = ((uint)Mask & 0x0FFFFFFF); 29 | blocks[0] |= byteMask; 30 | 31 | return GetBlocksAsString(blocks); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/Opcode9Arithmetic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | /* The types located in this file are based of the Atmosphère cheat implementation: 5 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 6 | */ 7 | 8 | namespace CheatASM 9 | { 10 | public class Opcode9Arithmetic : CheatOpcode 11 | { 12 | public BitWidthType BitWidth; 13 | public RegisterArithmeticType MathType; 14 | public uint RegisterDest; 15 | public uint RegisterLeft; 16 | public uint RegisterRight; 17 | public ulong Value; 18 | public bool RightHandRegister; 19 | public bool NoRightHandOperand; 20 | public Opcode9Arithmetic() { } 21 | 22 | public Opcode9Arithmetic(uint[] blocks) 23 | { 24 | BitWidth = (BitWidthType)GetNibble(blocks[0], 2); 25 | MathType = (RegisterArithmeticType)GetNibble(blocks[0], 3); 26 | RegisterDest = GetNibble(blocks[0], 4); 27 | RegisterLeft = GetNibble(blocks[0], 5); 28 | 29 | if (GetNibble(blocks[0], 6) == 0) 30 | { 31 | if (MathType == RegisterArithmeticType.not || MathType == RegisterArithmeticType.copy) 32 | { 33 | NoRightHandOperand = true; 34 | } 35 | RightHandRegister = true; 36 | RegisterRight = GetNibble(blocks[0], 7); 37 | } 38 | else 39 | { 40 | RightHandRegister = false; 41 | if (BitWidth == BitWidthType.q) 42 | { 43 | Value = ((UInt64)blocks[1] << 32) | blocks[2]; 44 | } 45 | else 46 | { 47 | Value = blocks[1]; 48 | } 49 | } 50 | } 51 | 52 | public override string ToASM() 53 | { 54 | StringBuilder sb = new StringBuilder(); 55 | sb.Append(Enum.GetName(typeof(RegisterArithmeticType), MathType)); 56 | sb.Append(".").Append(Enum.GetName(typeof(BitWidthType), BitWidth)); 57 | sb.Append(" R").Append(RegisterDest.ToString("X")); 58 | sb.Append(", R").Append(RegisterLeft.ToString("X")); 59 | 60 | /* don't print a right hand operand for not/copy which */ 61 | if (MathType != RegisterArithmeticType.not && MathType != RegisterArithmeticType.copy) 62 | { 63 | if (RightHandRegister) 64 | { 65 | sb.Append(", R").Append(RegisterRight.ToString("X")); 66 | } 67 | else 68 | { 69 | sb.Append(", 0x").Append(Value.ToString("X")); 70 | } 71 | } 72 | 73 | return sb.ToString(); 74 | } 75 | 76 | public override string ToByteString() 77 | { 78 | uint[] blocks = null; 79 | if (BitWidth == BitWidthType.q && RightHandRegister == false) 80 | { 81 | blocks = new uint[3]; 82 | } else 83 | { 84 | if (MathType == RegisterArithmeticType.not || MathType == RegisterArithmeticType.copy || RightHandRegister) 85 | { 86 | blocks = new uint[1]; 87 | } else 88 | { 89 | blocks = new uint[2]; 90 | } 91 | 92 | } 93 | 94 | SetNibble(ref blocks[0], 1, 9); 95 | SetNibble(ref blocks[0], 2, ((uint)BitWidth & 0xF)); 96 | SetNibble(ref blocks[0], 3, ((uint)MathType & 0xF)); 97 | SetNibble(ref blocks[0], 4, ((uint)RegisterDest & 0xF)); 98 | SetNibble(ref blocks[0], 5, ((uint)RegisterLeft & 0xF)); 99 | if (RightHandRegister || NoRightHandOperand) 100 | { 101 | SetNibble(ref blocks[0], 6, 0); 102 | SetNibble(ref blocks[0], 7, ((uint)RegisterRight & 0xF)); 103 | } else 104 | { 105 | SetNibble(ref blocks[0], 6, 1); 106 | SetNibble(ref blocks[0], 7, 0); 107 | } 108 | SetNibble(ref blocks[0], 8, 0); 109 | 110 | /* no immediate blocks for Not and Copy operations */ 111 | if (blocks.Length > 1) 112 | { 113 | if (BitWidth == BitWidthType.q) 114 | { 115 | blocks[1] = (uint)(Value >> 32); 116 | blocks[2] = (uint)(Value & 0xFFFFFFFF); 117 | } 118 | else 119 | { 120 | blocks[1] = (UInt32)Value; 121 | } 122 | 123 | } 124 | return GetBlocksAsString(blocks); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/OpcodeAStoreRegToAddress.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | /* The types located in this file are based of the Atmosphère cheat implementation: 5 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 6 | */ 7 | 8 | namespace CheatASM 9 | { 10 | public class OpcodeAStoreRegToAddress : CheatOpcode 11 | { 12 | public BitWidthType BitWidth; 13 | public MemoryAccessType MemType; 14 | public uint SourceRegister; 15 | public uint AddressRegister; 16 | public bool IncrementFlag; 17 | public uint OffsetType; 18 | public uint OffsetRegister; 19 | public ulong RelativeAddress; 20 | 21 | public OpcodeAStoreRegToAddress() { } 22 | 23 | public OpcodeAStoreRegToAddress(uint[] blocks) 24 | { 25 | BitWidth = (BitWidthType)GetNibble(blocks[0], 2); 26 | SourceRegister = GetNibble(blocks[0], 3); 27 | AddressRegister = GetNibble(blocks[0], 4); 28 | IncrementFlag = GetNibble(blocks[0], 5) == 1; 29 | OffsetType = GetNibble(blocks[0], 6); 30 | switch(OffsetType) 31 | { 32 | case 1: 33 | OffsetRegister = GetNibble(blocks[0], 7); 34 | break; 35 | case 2: 36 | RelativeAddress = ((UInt64)(blocks[0] & 0xF) << 32) + blocks[1]; 37 | break; 38 | case 3: 39 | MemType = (MemoryAccessType)GetNibble(blocks[0], 7); 40 | break; 41 | case 4: 42 | MemType = (MemoryAccessType)GetNibble(blocks[0], 7); 43 | RelativeAddress = ((UInt64)(blocks[0] & 0xF) << 32) + blocks[1]; 44 | break; 45 | case 5: 46 | MemType = (MemoryAccessType)GetNibble(blocks[0], 7); 47 | RelativeAddress = ((UInt64)(blocks[0] & 0xF) << 32) + blocks[1]; 48 | break; 49 | } 50 | 51 | } 52 | 53 | public override string ToASM() 54 | { 55 | StringBuilder sb = new StringBuilder(); 56 | sb.Append("mov.").Append(Enum.GetName(typeof(BitWidthType), BitWidth)); 57 | 58 | switch (OffsetType) 59 | { 60 | case 0: 61 | sb.Append(" [R").Append(AddressRegister.ToString("X")); 62 | sb.Append("], "); 63 | break; 64 | case 1: 65 | sb.Append(" [R").Append(AddressRegister.ToString("X")); 66 | sb.Append(" + R").Append(OffsetRegister.ToString("X")).Append("], "); 67 | break; 68 | case 2: 69 | sb.Append(" [R").Append(AddressRegister.ToString("X")); 70 | sb.Append(" + 0x").Append(RelativeAddress.ToString("X")).Append("], "); 71 | break; 72 | case 3: 73 | sb.Append(" [").Append(Enum.GetName(typeof(MemoryAccessType),MemType)); 74 | sb.Append(" + R").Append(AddressRegister.ToString("X")).Append("], "); 75 | break; 76 | case 4: 77 | sb.Append(" [").Append(Enum.GetName(typeof(MemoryAccessType), MemType)); 78 | sb.Append(" + 0x").Append(RelativeAddress.ToString("X")).Append("], "); 79 | break; 80 | case 5: 81 | sb.Append(" [").Append(Enum.GetName(typeof(MemoryAccessType), MemType)); 82 | sb.Append(" + R").Append(AddressRegister.ToString("X")); 83 | sb.Append(" + 0x").Append(RelativeAddress.ToString("X")).Append("], "); ; 84 | break; 85 | } 86 | 87 | sb.Append("R").Append(SourceRegister.ToString("X")); 88 | 89 | if (IncrementFlag) 90 | { 91 | sb.Append(" inc"); 92 | } 93 | return sb.ToString(); 94 | } 95 | 96 | public override string ToByteString() 97 | { 98 | uint[] blocks = null; 99 | 100 | switch (OffsetType) 101 | { 102 | case 0: 103 | blocks = new uint[1]; 104 | break; 105 | case 1: 106 | blocks = new uint[1]; 107 | break; 108 | case 2: 109 | blocks = new uint[2]; 110 | break; 111 | case 3: 112 | blocks = new uint[1]; 113 | break; 114 | case 4: 115 | blocks = new uint[2]; 116 | break; 117 | case 5: 118 | blocks = new uint[2]; 119 | break; 120 | } 121 | 122 | SetNibble(ref blocks[0], 1, 0xA); 123 | SetNibble(ref blocks[0], 2, (uint)BitWidth & 0xF); 124 | SetNibble(ref blocks[0], 3, (uint)SourceRegister & 0xF); 125 | SetNibble(ref blocks[0], 4, (uint)AddressRegister & 0xF); 126 | 127 | if (IncrementFlag) 128 | { 129 | SetNibble(ref blocks[0], 5, 1); 130 | } else 131 | { 132 | SetNibble(ref blocks[0], 5, 0); 133 | } 134 | 135 | SetNibble(ref blocks[0], 6, (uint)OffsetType&0xF); 136 | 137 | switch (OffsetType) 138 | { 139 | case 0: 140 | SetNibble(ref blocks[0], 7, 0); 141 | SetNibble(ref blocks[0], 8, 0); 142 | break; 143 | case 1: 144 | SetNibble(ref blocks[0], 7, (uint)OffsetRegister & 0xF); 145 | SetNibble(ref blocks[0], 8, 0); 146 | break; 147 | case 2: 148 | SetNibble(ref blocks[0], 8, (uint)(RelativeAddress >> 32) & 0xF); 149 | blocks[1] = (uint)(RelativeAddress & 0xFFFFFFFF); 150 | break; 151 | case 3: 152 | SetNibble(ref blocks[0], 4, (uint)OffsetRegister); 153 | SetNibble(ref blocks[0], 7, (uint)MemType & 0xF); 154 | SetNibble(ref blocks[0], 8, 0); 155 | break; 156 | case 4: 157 | SetNibble(ref blocks[0], 7, (uint)MemType & 0xF); 158 | SetNibble(ref blocks[0], 8, (uint)(RelativeAddress >> 32) & 0xF); 159 | blocks[1] = (uint)(RelativeAddress & 0xFFFFFFFF); 160 | break; 161 | case 5: 162 | SetNibble(ref blocks[0], 4, (uint)OffsetRegister); 163 | SetNibble(ref blocks[0], 7, (uint)MemType & 0xF); 164 | SetNibble(ref blocks[0], 8, (uint)(RelativeAddress >> 32) & 0xF); 165 | blocks[1] = (uint)(RelativeAddress & 0xFFFFFFFF); 166 | break; 167 | } 168 | return GetBlocksAsString(blocks); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/OpcodeC0RegisterConditional.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | /* The types located in this file are based of the Atmosphère cheat implementation: 5 | https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/dmnt/source/dmnt_cheat_vm.hpp 6 | */ 7 | 8 | namespace CheatASM 9 | { 10 | public class OpcodeC0RegisterConditional : CheatOpcode 11 | { 12 | public BitWidthType BitWidth; 13 | public ConditionalComparisonType Condition; 14 | public uint SourceRegister; 15 | public uint OperandType; 16 | public MemoryAccessType MemType; 17 | public ulong AddressRegister; 18 | public ulong RelativeAddress; 19 | public uint OffsetRegister; 20 | public uint OtherRegister; 21 | public ulong Value; 22 | 23 | public OpcodeC0RegisterConditional() { } 24 | 25 | public OpcodeC0RegisterConditional(uint[] blocks) 26 | { 27 | /* C0TcSX## */ 28 | /* C0TcS0Ma aaaaaaaa */ 29 | /* C0TcS1Mr */ 30 | /* C0TcS2Ra aaaaaaaa */ 31 | /* C0TcS3Rr */ 32 | /* C0TcS400 VVVVVVVV (VVVVVVVV) */ 33 | /* C0 = opcode 0xC0 */ 34 | /* T = bit width */ 35 | /* c = condition type. */ 36 | /* S = source register. */ 37 | /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset register, */ 38 | /* 2 = register with relative offset, 3 = register with offset register, 4 = static value. */ 39 | /* M = memory type. */ 40 | /* a = relative address. */ 41 | /* r = offset register. */ 42 | /* V = value */ 43 | 44 | BitWidth = (BitWidthType)GetNibble(blocks[0], 3); 45 | Condition = (ConditionalComparisonType)GetNibble(blocks[0], 4); 46 | SourceRegister = GetNibble(blocks[0], 5); 47 | OperandType = GetNibble(blocks[0], 6); 48 | switch(OperandType) 49 | { 50 | case 0: 51 | MemType = (MemoryAccessType)GetNibble(blocks[0], 7); 52 | RelativeAddress = ((ulong)(blocks[0] & 0xF) << 32) | blocks[1]; 53 | break; 54 | case 1: 55 | MemType = (MemoryAccessType)GetNibble(blocks[0], 7); 56 | OffsetRegister = GetNibble(blocks[0], 8); 57 | break; 58 | case 2: 59 | AddressRegister = GetNibble(blocks[0], 7); 60 | RelativeAddress = ((ulong)(blocks[0] & 0xF) << 32) | blocks[1]; 61 | break; 62 | case 3: 63 | AddressRegister = GetNibble(blocks[0], 7); 64 | OffsetRegister = GetNibble(blocks[0], 8); 65 | break; 66 | case 4: 67 | if (BitWidth == BitWidthType.q) 68 | { 69 | Value = (((ulong)blocks[1]) << 32) | blocks[2]; 70 | } else 71 | { 72 | Value = blocks[1]; 73 | } 74 | break; 75 | case 5: 76 | OtherRegister = GetNibble(blocks[0], 7); 77 | break; 78 | } 79 | } 80 | 81 | public override string ToASM() 82 | { 83 | StringBuilder sb = new StringBuilder(); 84 | 85 | sb.Append(Enum.GetName(typeof(ConditionalComparisonType), Condition)); 86 | sb.Append(".").Append(Enum.GetName(typeof(BitWidthType), BitWidth)); 87 | sb.Append(" "); 88 | switch(OperandType) 89 | { 90 | case 0: 91 | /* C0TcS0M: ltb R0, [MAIN + 0x1234] */ 92 | sb.Append("R").Append(SourceRegister.ToString("X")).Append(", ["); 93 | sb.Append(Enum.GetName(typeof(MemoryAccessType), MemType)); 94 | if (RelativeAddress > 0) 95 | { 96 | sb.Append(" + 0x").Append(RelativeAddress.ToString("X")); 97 | } 98 | sb.Append("]"); 99 | break; 100 | case 1: 101 | /* C0TcS1Mr: ltb R0, [MAIN + R1]*/ 102 | sb.Append("R").Append(SourceRegister.ToString("X")).Append(", ["); 103 | sb.Append(Enum.GetName(typeof(MemoryAccessType), MemType)); 104 | sb.Append(" + R").Append(OffsetRegister.ToString("X")); 105 | sb.Append("]"); 106 | break; 107 | case 2: 108 | /* C0TcS2R: ltb R0, [R1 + 0x1234] */ 109 | sb.Append("R").Append(SourceRegister.ToString("X")).Append(", ["); 110 | sb.Append("R").Append(AddressRegister.ToString("X")); 111 | if (RelativeAddress > 0) 112 | { 113 | sb.Append(" + 0x").Append(RelativeAddress.ToString("X")); 114 | } 115 | sb.Append("]"); 116 | break; 117 | case 3: 118 | /* C0TcS3Rr: ltb R0, [R1 + R2] */ 119 | sb.Append("R").Append(SourceRegister.ToString("X")).Append(", ["); 120 | sb.Append("R").Append(AddressRegister.ToString("X")); 121 | sb.Append(" + R").Append(OffsetRegister.ToString("X")); 122 | sb.Append("]"); 123 | break; 124 | case 4: 125 | /* C0TcS400: ltb R0, 0x1234 */ 126 | sb.Append("R").Append(SourceRegister.ToString("X")).Append(", 0x"); 127 | sb.Append(Value.ToString("X")); 128 | break; 129 | case 5: 130 | sb.Append("R").Append(SourceRegister.ToString("X")).Append(", R"); 131 | sb.Append(OtherRegister.ToString("X")); 132 | break; 133 | } 134 | 135 | return sb.ToString(); 136 | } 137 | 138 | public override string ToByteString() 139 | { 140 | /* C0TcSX## */ 141 | /* C0TcS0Ma aaaaaaaa */ 142 | /* C0TcS1Mr */ 143 | /* C0TcS2Ra aaaaaaaa */ 144 | /* C0TcS3Rr */ 145 | /* C0TcS400 VVVVVVVV (VVVVVVVV) */ 146 | /* C0 = opcode 0xC0 */ 147 | /* T = bit width */ 148 | /* c = condition type. */ 149 | /* S = source register. */ 150 | /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset register, */ 151 | /* 2 = register with relative offset, 3 = register with offset register, 4 = static value. */ 152 | /* M = memory type. */ 153 | /* a = relative address. */ 154 | /* r = offset register. */ 155 | /* V = value */ 156 | 157 | uint[] blocks = null; 158 | if (OperandType == 0 || OperandType == 2) 159 | { 160 | blocks = new uint[2]; 161 | } else if (OperandType == 4) 162 | { 163 | if (BitWidth == BitWidthType.q) 164 | { 165 | blocks = new uint[3]; 166 | } 167 | else 168 | { 169 | blocks = new uint[2]; 170 | } 171 | } else 172 | { 173 | blocks = new uint[1]; 174 | } 175 | 176 | SetNibble(ref blocks[0], 1, 0xC); 177 | SetNibble(ref blocks[0], 2, 0x0); 178 | 179 | SetNibble(ref blocks[0], 3, (uint)BitWidth); 180 | SetNibble(ref blocks[0], 4, (uint)Condition); 181 | SetNibble(ref blocks[0], 5, (uint)SourceRegister); 182 | SetNibble(ref blocks[0], 6, (uint)OperandType); 183 | 184 | switch(OperandType) 185 | { 186 | case 0: 187 | SetNibble(ref blocks[0], 7, (uint)MemType); 188 | SetNibble(ref blocks[0], 8, (uint)(RelativeAddress >> 32) & 0xF); 189 | blocks[1] = (uint)(RelativeAddress & 0xFFFFFFFF); 190 | break; 191 | case 1: 192 | SetNibble(ref blocks[0], 7, (uint)MemType); 193 | SetNibble(ref blocks[0], 8, (uint)OffsetRegister); 194 | break; 195 | case 2: 196 | SetNibble(ref blocks[0], 7, (uint)AddressRegister); 197 | SetNibble(ref blocks[0], 8, (uint)(RelativeAddress >> 32) & 0xF); 198 | blocks[1] = (uint)(RelativeAddress & 0xFFFFFFFF); 199 | break; 200 | case 3: 201 | SetNibble(ref blocks[0], 7, (uint)AddressRegister); 202 | SetNibble(ref blocks[0], 8, (uint)OffsetRegister); 203 | break; 204 | case 4: 205 | SetNibble(ref blocks[0], 7, 0); 206 | SetNibble(ref blocks[0], 8, 0); 207 | 208 | if (BitWidth == BitWidthType.q) 209 | { 210 | blocks[1] = (uint)(Value >> 32); 211 | blocks[2] = (uint)(Value & 0xFFFFFFFF); 212 | } 213 | else 214 | { 215 | blocks[1] = (uint)(Value & 0xFFFFFFFF); 216 | } 217 | break; 218 | case 5: 219 | SetNibble(ref blocks[0], 7, (uint)OtherRegister); 220 | SetNibble(ref blocks[0], 8, 0); 221 | break; 222 | } 223 | 224 | 225 | return GetBlocksAsString(blocks); 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/OpcodeC1SaveRestoreReg.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace CheatASM 8 | { 9 | class OpcodeC1SaveRestoreReg : CheatOpcode 10 | { 11 | public OpcodeC1SaveRestoreReg() { } 12 | public OpcodeC1SaveRestoreReg(uint[] blocks) 13 | { 14 | DestinationIndex = GetNibble(blocks[0], 4); 15 | SourceIndex = GetNibble(blocks[0], 6); 16 | OperandType = GetNibble(blocks[0], 7); 17 | } 18 | 19 | public uint DestinationIndex; 20 | public uint SourceIndex; 21 | public uint OperandType; 22 | 23 | public override string ToASM() 24 | { 25 | switch(OperandType) 26 | { 27 | case 0: 28 | return $"load.reg R{DestinationIndex.ToString("X")}, 0x{SourceIndex.ToString("X")}"; 29 | case 1: 30 | return $"save.reg 0x{DestinationIndex.ToString("X")}, R{SourceIndex.ToString("X")}"; 31 | case 2: 32 | return $"clear.saved 0x{DestinationIndex.ToString("X")}"; 33 | case 3: 34 | return $"clear.reg R{DestinationIndex.ToString("X")}"; 35 | default: 36 | return "Error printing SaveResotreRegisterOpcode"; 37 | } 38 | } 39 | 40 | public override string ToByteString() 41 | { 42 | uint[] blocks = new uint[1]; 43 | SetNibble(ref blocks[0], 1, 0xC); 44 | SetNibble(ref blocks[0], 2, 0x1); 45 | SetNibble(ref blocks[0], 3, 0x0); 46 | SetNibble(ref blocks[0], 4, (DestinationIndex & 0xF)); 47 | SetNibble(ref blocks[0], 5, 0); 48 | SetNibble(ref blocks[0], 6, (SourceIndex & 0xF)); 49 | SetNibble(ref blocks[0], 7, (OperandType & 0xF)); 50 | SetNibble(ref blocks[0], 8, 0); 51 | 52 | return GetBlocksAsString(blocks); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/OpcodeC2SaveRestoreRegMask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace CheatASM 8 | { 9 | class OpcodeC2SaveRestoreRegMask : CheatOpcode 10 | { 11 | public OpcodeC2SaveRestoreRegMask() { } 12 | public OpcodeC2SaveRestoreRegMask(uint[] blocks) 13 | { 14 | OperandType = GetNibble(blocks[0], 3); 15 | 16 | var maskBits = (blocks[0] & 0xFFFF); 17 | 18 | for(var x = 0x0; x <= 0xF; x++) 19 | { 20 | RegMask[x] = (maskBits % 2 == 1); 21 | maskBits >>= 1; 22 | } 23 | } 24 | 25 | public bool[] RegMask = new bool[16]; 26 | public uint OperandType; 27 | 28 | public override string ToASM() 29 | { 30 | 31 | StringBuilder paramList = new(); 32 | 33 | for(var x =0; x <= 0xF; x++) 34 | { 35 | if (RegMask[x]) 36 | { 37 | if (OperandType == 2) 38 | { 39 | paramList.Append($"0x{x:X}, "); 40 | } 41 | else 42 | { 43 | paramList.Append($"R{x:X}, "); 44 | } 45 | } 46 | } 47 | 48 | paramList.Length -= 2; 49 | 50 | switch (OperandType) 51 | { 52 | case 0: 53 | return $"load.regs {paramList}"; 54 | case 1: 55 | return $"save.regs {paramList}"; 56 | case 2: 57 | return $"clear.saved {paramList}"; 58 | case 3: 59 | return $"clear.regs {paramList}"; 60 | default: 61 | return "Error printing SaveResotreRegisterOpcode"; 62 | } 63 | } 64 | 65 | public override string ToByteString() 66 | { 67 | uint[] blocks = new uint[1]; 68 | SetNibble(ref blocks[0], 1, 0xC); 69 | SetNibble(ref blocks[0], 2, 0x2); 70 | SetNibble(ref blocks[0], 3, OperandType); 71 | SetNibble(ref blocks[0], 4, 0); 72 | 73 | uint maskValue = 0; 74 | for(var x = 0xF; x >= 0x0; x--) 75 | { 76 | maskValue <<= 1; 77 | if (RegMask[x]) 78 | { 79 | maskValue++; 80 | } 81 | } 82 | blocks[0] |= maskValue; 83 | 84 | 85 | return GetBlocksAsString(blocks); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/OpcodeC3ReadWriteStaticReg.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace CheatASM 8 | { 9 | class OpcodeC3ReadWriteStaticReg : CheatOpcode 10 | { 11 | public uint RegIndex; 12 | public uint StaticRegIndex; 13 | public bool WriteMode = false; 14 | 15 | public OpcodeC3ReadWriteStaticReg() { } 16 | public OpcodeC3ReadWriteStaticReg(uint[] blocks) 17 | { 18 | RegIndex = blocks[0] & 0xF; 19 | StaticRegIndex = (blocks[0] >> 4) & 0x7F; 20 | WriteMode = (((blocks[0] >> 4) & 0x80) == 0x80); 21 | } 22 | public override string ToASM() 23 | { 24 | if (WriteMode) 25 | { 26 | return $"save.static SR{StaticRegIndex:X}, R{RegIndex:X}"; 27 | } else 28 | { 29 | return $"load.static R{RegIndex:X}, SR{StaticRegIndex:X}"; 30 | } 31 | } 32 | 33 | public override string ToByteString() 34 | { 35 | uint[] blocks = new uint[1]; 36 | SetNibble(ref blocks[0], 1, 0xC); 37 | SetNibble(ref blocks[0], 2, 0x3); 38 | 39 | blocks[0] |= ((StaticRegIndex | (uint)(WriteMode ? 0x80 : 0x0)) << 4); 40 | blocks[0] |= RegIndex; 41 | 42 | return GetBlocksAsString(blocks); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/OpcodeFF0PauseProcess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace CheatASM 8 | { 9 | class OpcodeFF0PauseProcess : CheatOpcode 10 | { 11 | public OpcodeFF0PauseProcess() { } 12 | public OpcodeFF0PauseProcess(uint[] blocks) { } 13 | public override string ToASM() 14 | { 15 | return "pause"; 16 | } 17 | 18 | public override string ToByteString() 19 | { 20 | uint[] blocks = new uint[1]; 21 | SetNibble(ref blocks[0], 1, 0xF); 22 | SetNibble(ref blocks[0], 2, 0xF); 23 | SetNibble(ref blocks[0], 3, 0); 24 | SetNibble(ref blocks[0], 4, 0); 25 | SetNibble(ref blocks[0], 5, 0); 26 | SetNibble(ref blocks[0], 6, 0); 27 | SetNibble(ref blocks[0], 7, 0); 28 | SetNibble(ref blocks[0], 8, 0); 29 | return GetBlocksAsString(blocks); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/OpcodeFF1ResumeProcess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace CheatASM 8 | { 9 | class OpcodeFF1ResumeProcess : CheatOpcode 10 | { 11 | public OpcodeFF1ResumeProcess() { } 12 | public OpcodeFF1ResumeProcess(uint[] blocks) { } 13 | public override string ToASM() 14 | { 15 | return "resume"; 16 | } 17 | 18 | public override string ToByteString() 19 | { 20 | uint[] blocks = new uint[1]; 21 | SetNibble(ref blocks[0], 1, 0xF); 22 | SetNibble(ref blocks[0], 2, 0xF); 23 | SetNibble(ref blocks[0], 3, 0x1); 24 | SetNibble(ref blocks[0], 4, 0); 25 | SetNibble(ref blocks[0], 5, 0); 26 | SetNibble(ref blocks[0], 6, 0); 27 | SetNibble(ref blocks[0], 7, 0); 28 | SetNibble(ref blocks[0], 8, 0); 29 | return GetBlocksAsString(blocks); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CheatASM/Opcodes/OpcodeFFFDebugLog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace CheatASM 8 | { 9 | class OpcodeFFFDebugLog : CheatOpcode 10 | { 11 | public BitWidthType BitWidth; 12 | public uint LogId; 13 | public uint OperandType; 14 | public MemoryAccessType MemType; 15 | public uint AddressRegister; 16 | public ulong RelativeAddress; 17 | public uint OffsetRegister; 18 | public uint ValueRegister; 19 | 20 | public OpcodeFFFDebugLog() { } 21 | public OpcodeFFFDebugLog(uint[] blocks) 22 | { 23 | BitWidth = (BitWidthType)GetNibble(blocks[0], 4); 24 | LogId = GetNibble(blocks[0], 5); 25 | OperandType = GetNibble(blocks[0], 6); 26 | 27 | switch(OperandType) 28 | { 29 | case 0: 30 | MemType = (MemoryAccessType)GetNibble(blocks[0], 7); 31 | RelativeAddress = (((ulong)(blocks[0] & 0xF) << 32) + blocks[1]); 32 | break; 33 | case 1: 34 | MemType = (MemoryAccessType)GetNibble(blocks[0], 7); 35 | OffsetRegister = GetNibble(blocks[0], 8); 36 | break; 37 | case 2: 38 | AddressRegister = GetNibble(blocks[0], 7); 39 | RelativeAddress = (((ulong)(blocks[0] & 0xF) << 32) + blocks[1]); 40 | break; 41 | case 3: 42 | AddressRegister = GetNibble(blocks[0], 7); 43 | OffsetRegister = GetNibble(blocks[0], 8); 44 | break; 45 | case 4: 46 | ValueRegister = GetNibble(blocks[0], 7); 47 | break; 48 | } 49 | } 50 | public override string ToASM() 51 | { 52 | switch (OperandType) 53 | { 54 | case 0: 55 | if (RelativeAddress > 0) 56 | { 57 | return $"log.{Enum.GetName(typeof(BitWidthType), BitWidth)} 0x{LogId:X}, [{Enum.GetName(typeof(MemoryAccessType), MemType)} + 0x{RelativeAddress:X}]"; 58 | }else 59 | { 60 | return $"log.{Enum.GetName(typeof(BitWidthType), BitWidth)} 0x{LogId:X}, [{Enum.GetName(typeof(MemoryAccessType), MemType)}]"; 61 | } 62 | case 1: 63 | return $"log.{Enum.GetName(typeof(BitWidthType), BitWidth)} 0x{LogId:X}, [{Enum.GetName(typeof(MemoryAccessType), MemType)} + R{OffsetRegister:X}]"; 64 | case 2: 65 | if (RelativeAddress > 0) 66 | { 67 | return $"log.{Enum.GetName(typeof(BitWidthType), BitWidth)} 0x{LogId:X}, [R{AddressRegister:X} + 0x{RelativeAddress:X}]"; 68 | }else 69 | { 70 | return $"log.{Enum.GetName(typeof(BitWidthType), BitWidth)} 0x{LogId:X}, [R{AddressRegister:X}]"; 71 | } 72 | case 3: 73 | return $"log.{Enum.GetName(typeof(BitWidthType), BitWidth)} 0x{LogId:X}, [R{AddressRegister:X} + R{OffsetRegister:X}]"; 74 | case 4: 75 | return $"log.{Enum.GetName(typeof(BitWidthType), BitWidth)} 0x{LogId:X}, R{ValueRegister:X}"; 76 | } 77 | throw new NotImplementedException(); 78 | } 79 | 80 | public override string ToByteString() 81 | { 82 | /* BitWidth = (BitWidthType)GetNibble(blocks[0], 4); 83 | LogId = GetNibble(blocks[0], 5); 84 | OperandType = GetNibble(blocks[0], 6); 85 | 86 | switch(OperandType) 87 | { 88 | case 0: 89 | MemType = (MemoryAccessType)GetNibble(blocks[0], 7); 90 | RelativeAddress = (((blocks[0] & 0xF) << 32) + blocks[1]); 91 | break; 92 | case 1: 93 | MemType = (MemoryAccessType)GetNibble(blocks[0], 7); 94 | OffsetRegister = GetNibble(blocks[0], 8); 95 | break; 96 | case 2: 97 | AddressRegister = GetNibble(blocks[0], 7); 98 | RelativeAddress = (((blocks[0] & 0xF) << 32) + blocks[1]); 99 | break; 100 | case 3: 101 | AddressRegister = GetNibble(blocks[0], 7); 102 | OffsetRegister = GetNibble(blocks[0], 8); 103 | break; 104 | case 4: 105 | ValueRegister = GetNibble(blocks[0], 7); 106 | break; 107 | } */ 108 | 109 | uint[] blocks = null; 110 | switch(OperandType) 111 | { 112 | case 0: 113 | case 2: 114 | blocks = new uint[2]; 115 | break; 116 | default: 117 | blocks = new uint[1]; 118 | break; 119 | } 120 | SetNibble(ref blocks[0], 1, 0xF); 121 | SetNibble(ref blocks[0], 2, 0xF); 122 | SetNibble(ref blocks[0], 3, 0xF); 123 | SetNibble(ref blocks[0], 4, (uint)BitWidth); 124 | SetNibble(ref blocks[0], 5, LogId); 125 | SetNibble(ref blocks[0], 6, OperandType); 126 | 127 | switch(OperandType) 128 | { 129 | case 0: 130 | SetNibble(ref blocks[0], 7, (uint)MemType); 131 | SetNibble(ref blocks[0], 8, (uint)((RelativeAddress >> 32) & 0xF)); 132 | blocks[1] = (uint)(RelativeAddress & 0xFFFFFFFF); 133 | break; 134 | case 1: 135 | SetNibble(ref blocks[0], 7, (uint)MemType); 136 | SetNibble(ref blocks[0], 8, OffsetRegister); 137 | break; 138 | case 2: 139 | SetNibble(ref blocks[0], 7, AddressRegister); 140 | SetNibble(ref blocks[0], 8, (uint)((RelativeAddress >> 32) & 0xF)); 141 | blocks[1] = (uint)(RelativeAddress & 0xFFFFFFFF); 142 | break; 143 | case 3: 144 | SetNibble(ref blocks[0], 7, AddressRegister); 145 | SetNibble(ref blocks[0], 8, OffsetRegister); 146 | break; 147 | case 4: 148 | SetNibble(ref blocks[0], 7, ValueRegister); 149 | SetNibble(ref blocks[0], 8, 0); 150 | break; 151 | } 152 | return GetBlocksAsString(blocks); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /CheatASM/Program.cs: -------------------------------------------------------------------------------- 1 | using Mono.Options; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Text.RegularExpressions; 10 | 11 | namespace CheatASM 12 | { 13 | class Program 14 | { 15 | static void RunREPL() 16 | { 17 | Console.Clear(); 18 | bool run = true; 19 | Console.WriteLine("CheatASM REPL Started."); 20 | Console.WriteLine("Enter blank line to execute, enter quit() to stop REPL."); 21 | List replLines = new List(); 22 | 23 | while(run) { 24 | Console.Write(">> "); 25 | var line = Console.ReadLine(); 26 | if (line.Length == 0) 27 | { 28 | Disassembler disasm = new Disassembler(); 29 | Assembler asm = new Assembler(); 30 | /* process replLines */ 31 | StringBuilder sb = new StringBuilder(); 32 | foreach (var l in replLines) 33 | { 34 | sb.AppendLine(l); 35 | } 36 | 37 | var fullText = sb.ToString(); 38 | if (Regex.IsMatch(fullText, @"^[0-9a-fA-F\s\r\n]+$",RegexOptions.Singleline)) 39 | { 40 | Console.WriteLine(disasm.DisassembleString(fullText)); 41 | } else 42 | { 43 | Console.WriteLine(asm.AssembleString(fullText)); 44 | } 45 | 46 | replLines.Clear(); 47 | } 48 | if (line.ToLower().Contains("quit")) 49 | { 50 | run = false; 51 | continue; 52 | } else 53 | { 54 | /* just add to list to process, we'll handle what to do later */ 55 | replLines.Add(line.Trim()); 56 | } 57 | } 58 | } 59 | 60 | static void Main(string[] args) 61 | { 62 | string[] resizableArray = new string[] { "a", "b", "c" }; 63 | 64 | Stack myStack = new Stack(); 65 | myStack.Push("a"); 66 | myStack.Push("b"); 67 | myStack.Push("c"); 68 | myStack.Pop(); 69 | myStack.Pop(); 70 | myStack.Pop(); 71 | 72 | Queue myQueue = new Queue(); 73 | myQueue.Enqueue("a"); 74 | myQueue.Enqueue("b"); 75 | myQueue.Enqueue("c"); 76 | myQueue.Dequeue(); 77 | myQueue.Dequeue(); 78 | myQueue.Dequeue(); 79 | 80 | bool help = false; 81 | string inputPath = ""; 82 | string outputPath = ""; 83 | bool disassemble = false; 84 | bool assemble = false; 85 | bool recursive = false; 86 | bool verbose = false; 87 | string text = ""; 88 | bool repl = false; 89 | OptionSet optSet = new OptionSet(); 90 | optSet.Add("?|help|h", "Prints out the options.", option => help = option != null); 91 | optSet.Add("d|disassemble", "Disassembler mode.", option => disassemble = option != null); 92 | optSet.Add("a|assemble", "Assembler mode.", option => assemble = option != null); 93 | optSet.Add("i=|in=", "Input File or Directory.", option => inputPath = option); 94 | optSet.Add("o=|out=", "Output File or Directory.", option => outputPath = option); 95 | optSet.Add("t=|text=", "String to dis/assemble.", option => text = option); 96 | optSet.Add("r|recursive", "Process directory recursively.", option => recursive = option != null); 97 | optSet.Add("v|verbose", "Verbose Logging.", option => verbose = option != null); 98 | optSet.Add("repl", "REPL mode", option => repl = option != null); 99 | optSet.Parse(args); 100 | 101 | if (repl) 102 | { 103 | RunREPL(); 104 | return; 105 | } 106 | 107 | /* movd [MAIN+R1+0x6C7634], 0x98967F */ 108 | 109 | if (assemble && disassemble) 110 | { 111 | Console.Error.WriteLine("You cannot specifiy both assembler and disassembler modes simultaneously."); 112 | return; 113 | } 114 | 115 | if (help) 116 | { 117 | Console.WriteLine("Usage: CheatASM -d/a -in FILE -out FILE"); 118 | optSet.WriteOptionDescriptions(Console.Error); 119 | } 120 | 121 | /* ensure input exists and determine if it is a directory or not */ 122 | bool isInputDir = false; 123 | bool textMode = false; 124 | if (Directory.Exists(inputPath)) 125 | { 126 | isInputDir = true; 127 | /* if you specified an input directory you must specifiy an output */ 128 | if (outputPath == "") 129 | { 130 | Console.Error.WriteLine("When processing a directoy an output directory *must* be specified."); 131 | return; 132 | } 133 | } 134 | else 135 | { 136 | if (File.Exists(inputPath)) 137 | { 138 | isInputDir = false; 139 | } 140 | else 141 | { 142 | if (text == "") 143 | { 144 | /* input path isn't an existing file or directory */ 145 | Console.Error.WriteLine("Unable to find the input path specified."); 146 | return; 147 | } else 148 | { 149 | textMode = true; 150 | } 151 | } 152 | } 153 | 154 | /* at this point we know the inputPath exists, and if its a folder or not */ 155 | 156 | Assembler asm = new Assembler(); 157 | Disassembler disasm = new Disassembler(); 158 | 159 | if (isInputDir) 160 | { 161 | string[] fileList = Directory.GetFiles(inputPath, "*.txt", new EnumerationOptions() { RecurseSubdirectories = recursive }); 162 | 163 | foreach (var file in fileList) 164 | { 165 | var relativePath = file.Replace(inputPath, ""); 166 | 167 | /* make sure folder exists */ 168 | var newFolderPath = outputPath + relativePath.Substring(0,relativePath.LastIndexOf(Path.DirectorySeparatorChar)); 169 | Directory.CreateDirectory(newFolderPath); 170 | if (verbose) 171 | { 172 | Console.WriteLine("Saving " + outputPath + relativePath + "..."); 173 | } 174 | if (assemble) 175 | { 176 | File.WriteAllText(outputPath + relativePath, asm.AssembleFile(file).ToString()); 177 | } else 178 | { 179 | File.WriteAllText(outputPath + relativePath, disasm.DisassembleFile(file)); 180 | } 181 | } 182 | Console.WriteLine("Processed " + fileList.Length + " files."); 183 | } 184 | else 185 | { 186 | /* dealing with a single file */ 187 | if (assemble) 188 | { 189 | if (outputPath != "") 190 | { 191 | if (textMode) 192 | { 193 | File.WriteAllText(outputPath, asm.AssembleString(text).ToString()); 194 | } 195 | else 196 | { 197 | File.WriteAllText(outputPath, asm.AssembleFile(inputPath).ToString()); 198 | } 199 | } 200 | else 201 | { 202 | if (textMode) 203 | { 204 | Console.Write(asm.AssembleString(text)); 205 | } 206 | else 207 | { 208 | Console.Write(asm.AssembleFile(inputPath)); 209 | } 210 | } 211 | }else 212 | { 213 | if (outputPath != "") 214 | { 215 | if (textMode) 216 | { 217 | File.WriteAllText(outputPath, disasm.DisassembleLine(text)); 218 | } 219 | else 220 | { 221 | File.WriteAllText(outputPath, disasm.DisassembleFile(inputPath)); 222 | } 223 | } 224 | else 225 | { 226 | if (textMode) 227 | { 228 | Console.Write(disasm.DisassembleLine(text)); 229 | } 230 | else 231 | { 232 | Console.Write(disasm.DisassembleFile(inputPath)); 233 | } 234 | } 235 | } 236 | 237 | 238 | } 239 | 240 | } 241 | 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /CheatASM/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | FileSystem 8 | Release 9 | Any CPU 10 | netcoreapp2.1 11 | bin\Release\netcoreapp2.1\publish\ 12 | false 13 | <_IsPortable>true 14 | 15 | -------------------------------------------------------------------------------- /CheatASM/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "CheatASM": { 4 | "commandName": "Project", 5 | "commandLineArgs": "-repl" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /CheatASM/examples/C1C2C3.asm: -------------------------------------------------------------------------------- 1 | someVar: .u32 0x0 2 | 3 | # 0xC1 Tests 4 | # save.reg 0x3, R1 5 | # load.reg R1, 0x1 6 | # clear.reg R1 7 | # clear.saved 0x1 8 | 9 | # C2 Tests 10 | save.regs R1, someVar, R3 11 | load.regs R1, someVar, R3 12 | clear.regs R1, someVar, R3 13 | clear.saved 0x1, 0x2, 0x3 14 | 15 | # 0xC3 Read/Write Static opcode 16 | # save.static SR1, R2 17 | # load.static R2, SR1 18 | 19 | # 0xFFF Debug Log opcode 20 | # log.d 0x3, [MAIN + R3] 21 | # log.d 0x3, [HEAP + 0x4] 22 | # log.b 0x1, R4 23 | # log.d 0x2, [R4 + 0x123] -------------------------------------------------------------------------------- /CheatASM/examples/asm_skeleton.asm: -------------------------------------------------------------------------------- 1 | .title {1234} 2 | .build {1234} 3 | 4 | .cheat master "Setup" 5 | mov.q R0, [MAIN + 0x1234] 6 | 7 | .cheat "Always 10 coins" 8 | mov.d R1, 0xA 9 | mov.d [R0 + 0x1234], R1 -------------------------------------------------------------------------------- /CheatASM/examples/instructions_only.asm: -------------------------------------------------------------------------------- 1 | mov.d R1, 0xA 2 | mov.d [R0 + 0x1234], R1 -------------------------------------------------------------------------------- /CheatASM/examples/variables.asm: -------------------------------------------------------------------------------- 1 | .title {1234} 2 | .build {1234} 3 | 4 | floatTest: .f32 4.83 5 | mainOffset: .u32 const 0x1234 6 | coinOffset: .u32 const 0x12 7 | ten: .u32 0xA 8 | 9 | .cheat master "Setup" 10 | mov.d [R0 + 0x123], floatTest 11 | mov.q R0, [MAIN + mainOffset] 12 | 13 | # .cheat "Always 10 coins" 14 | # mov.d [R0 + coinOffset], ten -------------------------------------------------------------------------------- /CheatASMTests/AssemblerTests.cs: -------------------------------------------------------------------------------- 1 | using Antlr4.Runtime; 2 | using CheatASM; 3 | using NUnit.Framework; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | 8 | namespace CheatASMTests 9 | { 10 | public class AssemblerTests 11 | { 12 | Assembler asm; 13 | [SetUp] 14 | public void Setup() 15 | { 16 | asm = new Assembler(); 17 | } 18 | 19 | [Test] 20 | public void TestOpcode0() 21 | { 22 | /* MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (memType=MEM_TYPE) PLUS_SIGN (regOffset=regRef) (PLUS_SIGN (numOffset=numRef))? RSQUARE COMMA (value=numRef)*/ 23 | /* b w d q */ 24 | /* MAIN HEAP */ 25 | Dictionary instructions = new() 26 | { 27 | { "mov.d [MAIN + R2], 0x123", "04020000 00000000 00000123" }, 28 | { "mov.q [MAIN + R2], 0x123", "08020000 00000000 00000000 00000123" }, 29 | { "mov.d [MAIN + R8 + 0x7], 0x345", "04080000 00000007 00000345" }, 30 | { "mov.w [HEAP + R7], 0x236", "02170000 00000000 00000236" }, 31 | { "mov.b [HEAP + R7 + 0x9], 0xFE", "01170000 00000009 000000FE" }, 32 | }; 33 | 34 | foreach (var kvp in instructions) 35 | { 36 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 37 | Assert.AreEqual(assembledInstruction, kvp.Value,$"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 38 | } 39 | 40 | Assert.Throws(() => 41 | { 42 | /* tests bit width constraint on literals */ 43 | asm.AssembleSingleInstruction("mov.b [HEAP + R7 + 0x9], 0x1234"); 44 | }); 45 | 46 | } 47 | 48 | [Test] 49 | public void TestOpcode1() 50 | { 51 | /* (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) LSQUARE(memType=MEM_TYPE)PLUS_SIGN(offset=numRef)RSQUARE COMMA (value=numRef); */ 52 | /* gt ge lt le eq ne */ 53 | /* b w d q */ 54 | /* MAIN HEAP */ 55 | Dictionary instructions = new() 56 | { 57 | { "gt.d [MAIN + 0x123], 0x456", "14010000 00000123 00000456" }, 58 | { "ge.b [HEAP + 0x123], 0x56", "11120000 00000123 00000056" }, 59 | { "lt.q [MAIN + 0x123456789A], 0x4564123478", "18030012 3456789A 00000045 64123478" }, 60 | { "le.d [MAIN + 0x123], 0x456", "14040000 00000123 00000456" }, 61 | { "eq.d [MAIN + 0x123], 0x456", "14050000 00000123 00000456" }, 62 | { "ne.d [MAIN + 0x123], 0x456", "14060000 00000123 00000456" }, 63 | }; 64 | 65 | Assert.Throws(() => 66 | { 67 | /* tests bit width constraint on literals */ 68 | asm.AssembleSingleInstruction("gt.b[MAIN + 0x123], 0x456"); 69 | }); 70 | 71 | foreach (var kvp in instructions) 72 | { 73 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 74 | Assert.AreEqual(assembledInstruction, kvp.Value, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 75 | } 76 | } 77 | 78 | [Test] 79 | public void TestOpcode2() 80 | { 81 | Assert.AreEqual(asm.AssembleSingleInstruction("endcond"), "20000000"); 82 | Assert.AreEqual(asm.AssembleSingleInstruction("else"), "21000000"); 83 | } 84 | 85 | [Test] 86 | public void TestOpcode3() 87 | { 88 | /* LOOP (register=regRef) COMMA(value=numRef) | (endloop=END_LOOP) (register=regRef);*/ 89 | 90 | Dictionary instructions = new() 91 | { 92 | { "loop R7, 0x6", "30070000 00000006" }, 93 | { "endloop R6", "31060000" } 94 | }; 95 | 96 | foreach (var kvp in instructions) 97 | { 98 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 99 | Assert.AreEqual(assembledInstruction, kvp.Value, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 100 | } 101 | } 102 | 103 | [Test] 104 | public void TestOpcode4() 105 | { 106 | /* MOVE (DOT BIT_WIDTH)? (register=regRef) COMMA (value=numRef) # opCode4 */ 107 | 108 | Dictionary instructions = new() 109 | { 110 | { "mov.b R0, 0x1", "40000000 00000000 00000001" }, 111 | { "mov R2, 0x3", "40020000 00000000 00000003" }, 112 | { "mov.d R7, 0x12345678", "40070000 00000000 12345678" }, 113 | { "mov.q RA, 0x1122334455667788", "400A0000 11223344 55667788" } 114 | }; 115 | 116 | Assert.Throws(() => 117 | { 118 | /* tests bit width constraint on literals */ 119 | asm.AssembleSingleInstruction("mov.b R0, 0x123"); 120 | }); 121 | 122 | foreach (var kvp in instructions) 123 | { 124 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 125 | Assert.AreEqual(assembledInstruction, kvp.Value, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 126 | } 127 | } 128 | 129 | [Test] 130 | public void TestOpcode5() 131 | { 132 | /* MOVE DOT (bitWidth=BIT_WIDTH) (register=regRef) COMMA LSQUARE (memType=MEM_TYPE) (PLUS_SIGN (numOffset=numRef))? RSQUARE 133 | MOVE DOT (bitWidth=BIT_WIDTH) (register=regRef) COMMA LSQUARE (baseRegister=regRef) (PLUS_SIGN (numOffset=numRef))? RSQUARE */ 134 | Dictionary instructions = new() 135 | { 136 | { "mov.b R0, [MAIN + 0x12]", "51000000 00000012" }, 137 | { "mov.d R0, [MAIN + 0x0]", "54000000 00000000" }, 138 | { "mov.w R0, [MAIN]", "52000000 00000000" }, 139 | { "mov.q R0, [HEAP + 0x1122334455]", "58100011 22334455" }, 140 | { "mov.b R0, [R0 + 0x12]", "51001000 00000012" }, 141 | { "mov.d R0, [R0 + 0x0]", "54001000 00000000" }, 142 | { "mov.w R0, [R0]", "52001000 00000000" }, 143 | 144 | }; 145 | 146 | 147 | Assert.Throws(() => 148 | { 149 | asm.AssembleSingleInstruction("mov.b R0, [R7 + 0x12]"); 150 | }); 151 | 152 | foreach (var kvp in instructions) 153 | { 154 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 155 | Assert.AreEqual(assembledInstruction, kvp.Value, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 156 | } 157 | 158 | 159 | } 160 | 161 | [Test] 162 | public void TestOpcode6() 163 | { 164 | /* MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (base=regRef) (PLUS_SIGN (regOffset=regRef))? RSQUARE COMMA (value=numRef) (increment=INCREMENT)? */ 165 | Dictionary instructions = new() 166 | { 167 | { "mov.b [R7], 0x12", "61070000 00000000 00000012" }, 168 | { "mov.w [R4], 0x12 inc", "62041000 00000000 00000012" }, 169 | { "mov.d [R3 + R2], 0x12", "64030120 00000000 00000012" }, 170 | { "mov.q [RB + R2], 0x1122334455667788", "680B0120 11223344 55667788" }, 171 | }; 172 | 173 | 174 | foreach (var kvp in instructions) 175 | { 176 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 177 | Assert.AreEqual(assembledInstruction, kvp.Value, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 178 | } 179 | } 180 | 181 | [Test] 182 | public void TestOpcode7() 183 | { 184 | /* (func=LEGACY_ARITHMETIC) DOT (bitWidth=BIT_WIDTH) (register=regRef) COMMA (value=numRef); */ 185 | /* add sub mul lsh rsh */ 186 | Dictionary instructions = new() 187 | { 188 | { "add.b R7, 0x12", "71070000 00000012" }, 189 | { "sub.w R4, 0x12", "72041000 00000012" }, 190 | { "mul.d R3, 0x12", "74032000 00000012" }, 191 | { "lsh.q RB, 0x11223344", "780B3000 11223344" }, 192 | { "rsh.d R4, 0x55667788", "74044000 55667788" }, 193 | }; 194 | 195 | 196 | foreach (var kvp in instructions) 197 | { 198 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 199 | Assert.AreEqual(assembledInstruction, kvp.Value, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 200 | } 201 | 202 | 203 | Assert.Throws(() => 204 | { 205 | asm.AssembleSingleInstruction("mul.b R3, 0x1234"); 206 | }); 207 | } 208 | 209 | [Test] 210 | public void TestOpcode8() 211 | { 212 | foreach(var val in Enum.GetNames(typeof(KeyMask))) 213 | { 214 | var enumVal = (uint)(Enum.Parse(typeof(KeyMask), val)); 215 | Assert.AreEqual(asm.AssembleSingleInstruction($"keycheck {val})"), $"8{enumVal.ToString("X7")}"); 216 | } 217 | 218 | Assert.Throws(() => 219 | { 220 | asm.AssembleSingleInstruction("keycheck Q"); 221 | }); 222 | } 223 | 224 | 225 | [Test] 226 | public void TestOpcode9() 227 | { 228 | /* (func=ARITHMETIC) DOT (bitWidth=BIT_WIDTH) (dest=regRef) COMMA (leftReg=regRef) COMMA (right=anyRef); */ 229 | 230 | Dictionary instructions = new() 231 | { 232 | { "add.b R7, R3, 0x12", "91073100 00000012" }, 233 | { "sub.w R4, R1, 0x12", "92141100 00000012" }, 234 | { "mul.d R3, R0, 0x12", "94230100 00000012" }, 235 | { "lsh.q RB, RD, 0x112233445566", "983BD100 00001122 33445566" }, 236 | { "rsh.d R4, R7, 0x55667788", "94447100 55667788" }, 237 | { "and.b R7, R3, 0x12", "91573100 00000012" }, 238 | { "or.b R7, R3, 0x12", "91673100 00000012" }, 239 | { "xor.b R7, R3, 0x12", "91873100 00000012" }, 240 | { "add.b R7, R3, R2", "91073020" }, 241 | { "sub.w R4, R1, R2", "92141020" }, 242 | { "mul.d R3, R0, R7", "94230070" }, 243 | { "lsh.q RB, RD, R8", "983BD080" }, 244 | { "rsh.d R4, R7, R1", "94447010" }, 245 | { "and.b R7, R3, R0", "91573000" }, 246 | { "or.b R7, R3, R4", "91673040" }, 247 | { "xor.b R7, R3, R6", "91873060" }, 248 | { "not.b R7, R3", "91773000" }, 249 | { "copy.b R7, R3", "91973000" }, 250 | }; 251 | 252 | foreach (var kvp in instructions) 253 | { 254 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 255 | Assert.AreEqual(assembledInstruction, kvp.Value, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 256 | } 257 | 258 | 259 | } 260 | 261 | [Test] 262 | public void TestOpcodeA() 263 | { 264 | /* 265 | MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (base=regRef) (PLUS_SIGN (regOffset=regRef))? RSQUARE COMMA (regValue=regRef) (increment=INCREMENT)? #opCodeA 266 | MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (base=regRef) (PLUS_SIGN (numOffset=numRef))? RSQUARE COMMA (regValue=regRef) (increment=INCREMENT)? #opCodeA 267 | MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (memType=MEM_TYPE) (PLUS_SIGN (regOffset=regRef))? (PLUS_SIGN (numOffset=numRef))? RSQUARE COMMA (regValue=regRef) #opCodeA; 268 | */ 269 | Dictionary instructions = new() 270 | { 271 | { "mov.d [R0], R1", "A4100000" }, 272 | { "mov.d [R9], R2 inc", "A4291000" }, 273 | { "mov.d [RA+ R1], R3", "A43A0110" }, 274 | { "mov.d [RB + R1], R4 inc", "A44B1110" }, 275 | { "mov.d [RC + 0x123], R5", "A45C0200 00000123" }, 276 | { "mov.d [RD + 0x123], R6 inc", "A46D1200 00000123" }, 277 | { "mov.d [MAIN + RE], R7", "A47E0300" }, 278 | { "mov.d [MAIN + 0x123], R7", "A4700400 00000123" }, 279 | { "mov.d [HEAP + RF + 0x123456789], R8", "A48F0511 23456789" } 280 | }; 281 | 282 | foreach (var kvp in instructions) 283 | { 284 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 285 | Assert.AreEqual(assembledInstruction, kvp.Value, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 286 | } 287 | 288 | } 289 | 290 | [Test] 291 | public void TestOpcodeC0() 292 | { 293 | /* 294 | (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) (source=regRef) COMMA LSQUARE (memType=MEM_TYPE) PLUS_SIGN (offset=anyRef) RSQUARE 295 | (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) (source=regRef) COMMA LSQUARE (addrReg=regRef) (PLUS_SIGN (offset=anyRef))? RSQUARE 296 | (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) (source=regRef) COMMA (value=anyRef); 297 | */ 298 | 299 | Dictionary instructions = new() 300 | { 301 | { ".b R0, R1", "C01_0510" }, 302 | { ".w R0, 0x123", "C02_0400 00000123" }, 303 | { ".d R0, [R1]", "C04_0210 00000000" }, 304 | { ".q R0, [R1 + 0x123]", "C08_0210 00000123" }, 305 | { ".b R0, [R1 + R2]", "C01_0312" }, 306 | { ".w R0, [MAIN + 0x123]", "C02_0000 00000123" }, 307 | { ".d R0, [HEAP + 0x123]", "C04_0010 00000123" }, 308 | { ".q R0, [MAIN]", "C08_0000 00000000" }, 309 | { ".b R0, [MAIN + R0]", "C01_0100" }, 310 | 311 | }; 312 | string[] comparisons = new string[] { "gt", "ge", "lt", "le", "eq", "ne" }; 313 | foreach (var kvp in instructions) 314 | { 315 | foreach (var comp in comparisons) 316 | { 317 | var testInstruction = kvp.Key; 318 | var testAnswer = kvp.Value; 319 | testInstruction = $"{comp}{testInstruction}"; 320 | testAnswer = testAnswer.Replace('_', ((uint)(Enum.Parse(typeof(ConditionalComparisonType), comp))).ToString()[0]); 321 | 322 | var assembledInstruction = asm.AssembleSingleInstruction(testInstruction); 323 | Assert.AreEqual(assembledInstruction, testAnswer, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 324 | } 325 | } 326 | } 327 | 328 | [Test] 329 | public void TestOpcodeC1() 330 | { 331 | /* 332 | (func=SAVE) DOT (type=REG) (index=numRef) COMMA (reg=regRef) 333 | | (func=LOAD) DOT (type=REG) (reg=regRef) COMMA (index=numRef) 334 | | (func=CLEAR) DOT (type=REG) (reg=regRef) 335 | | (func=CLEAR) DOT (type=SAVED) (index=numRef) 336 | */ 337 | 338 | Dictionary instructions = new() 339 | { 340 | { "save.reg 0x1, R3", "C1010310" }, 341 | { "load.reg R3, 0x1", "C1030100" }, 342 | { "clear.reg R3", "C1030030" }, 343 | { "clear.saved 0x1", "C1010020" }, 344 | }; 345 | 346 | foreach (var kvp in instructions) 347 | { 348 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 349 | Assert.AreEqual(assembledInstruction, kvp.Value, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 350 | } 351 | 352 | 353 | 354 | } 355 | 356 | [Test] 357 | public void TestOpcodeC2() 358 | { 359 | /* 360 | (func=SAVE) DOT (type=REG) (index=numRef) COMMA (reg=regRef) 361 | | (func=LOAD) DOT (type=REG) (reg=regRef) COMMA (index=numRef) 362 | | (func=CLEAR) DOT (type=REG) (reg=regRef) 363 | | (func=CLEAR) DOT (type=SAVED) (index=numRef) 364 | */ 365 | 366 | Dictionary instructions = new() 367 | { 368 | { "save.regs R1", "C2100002" }, 369 | { "save.regs R1,R2", "C2100006" }, 370 | { "save.regs R0, R1,R2,R3,R4,R5,R6,R7,R8,R9,RA,RB,RC,RD,RE,RF", "C210FFFF" }, 371 | 372 | { "load.regs R1", "C2000002" }, 373 | { "load.regs R1,R2", "C2000006" }, 374 | { "load.regs R0,R1,R2,R3,R4,R5,R6,R7,R8,R9,RA,RB,RC,RD,RE,RF", "C200FFFF" }, 375 | 376 | { "clear.regs R1", "C2300002" }, 377 | { "clear.regs R1,R2", "C2300006" }, 378 | { "clear.regs R0,R1,R2,R3,R4,R5,R6,R7,R8,R9,RA,RB,RC,RD,RE,RF", "C230FFFF" }, 379 | 380 | 381 | { "clear.saved 0x1,0x2,0x3,0x4", "C220001E" }, 382 | }; 383 | 384 | foreach (var kvp in instructions) 385 | { 386 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 387 | Assert.AreEqual(assembledInstruction, kvp.Value, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 388 | } 389 | 390 | 391 | 392 | } 393 | 394 | [Test] 395 | public void TestOpcodeC3() 396 | { 397 | /* 398 | (func=SAVE) DOT (type=STATIC) (sreg=SREGISTER) COMMA (reg=regRef) 399 | (func=LOAD) DOT (type=STATIC) (reg=regRef) COMMA (sreg=SREGISTER); 400 | */ 401 | 402 | Dictionary instructions = new() 403 | { 404 | { "save.static SR1, R0", "C3000810" }, 405 | { "load.static R0, SR7", "C3000070" }, 406 | { "save.static SR7F, R1", "C3000FF1" }, 407 | { "load.static R6, SR7F", "C30007F6" }, 408 | 409 | }; 410 | 411 | foreach (var kvp in instructions) 412 | { 413 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 414 | Assert.AreEqual(assembledInstruction, kvp.Value, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 415 | } 416 | } 417 | 418 | [Test] 419 | public void TestOpcodeFF0() 420 | { 421 | /* opCodeFF0: (func=PAUSE); */ 422 | Assert.AreEqual(asm.AssembleSingleInstruction("pause"), "FF000000"); 423 | } 424 | 425 | [Test] 426 | public void TestOpcodeFF1() 427 | { 428 | /* opCodeFF1: (func=RESUME); */ 429 | Assert.AreEqual(asm.AssembleSingleInstruction("resume"), "FF100000"); 430 | } 431 | 432 | [Test] 433 | public void TestOpcodeFFF() 434 | { 435 | /* 436 | opCodeFFF: (func=LOG) DOT (bitWidth=BIT_WIDTH) (id=HEX_NUMBER) COMMA LSQUARE (memType=MEM_TYPE) (PLUS_SIGN (offset=anyRef))? RSQUARE 437 | (func=LOG) DOT (bitWidth=BIT_WIDTH) (id=HEX_NUMBER) COMMA LSQUARE (addrReg=regRef) (PLUS_SIGN (offset=anyRef))? RSQUARE 438 | (func=LOG) DOT (bitWidth=BIT_WIDTH) (id=HEX_NUMBER) COMMA (value=regRef); 439 | */ 440 | 441 | Dictionary instructions = new() 442 | { 443 | { "log.b 0x1, [MAIN]", "FFF11000 00000000" }, 444 | { "log.w 0x1, [MAIN + 0x123]", "FFF21000 00000123" }, 445 | { "log.d 0x1, [MAIN + R3]", "FFF41103" }, 446 | { "log.q 0x1, [HEAP + 0x123]", "FFF81010 00000123" }, 447 | { "log.b 0x1, [R2]", "FFF11220 00000000" }, 448 | { "log.b 0x1, [R2 + R3]", "FFF11323" }, 449 | { "log.w 0x1, [R4 + 0x123]", "FFF21240 00000123" }, 450 | { "log.d 0x1, R7", "FFF41470" }, 451 | }; 452 | 453 | foreach (var kvp in instructions) 454 | { 455 | var assembledInstruction = asm.AssembleSingleInstruction(kvp.Key); 456 | Assert.AreEqual(assembledInstruction, kvp.Value, $"Assembly of '{kvp.Key}' gave '{assembledInstruction}' instead of '{kvp.Value}'"); 457 | } 458 | 459 | 460 | } 461 | 462 | [Test] 463 | public void TestVariables() 464 | { 465 | var programText = @" 466 | floatTest: .f32 4.83 467 | mainOffset: .u32 const 0x1234 468 | coinOffset: .u32 const 0x12 469 | ten: .u32 0xA 470 | 471 | .cheat master ""Setup"" 472 | mov.d[R0 + 0x123], floatTest 473 | mov.q R0, [MAIN + mainOffset] 474 | 475 | .cheat ""Sample"" 476 | mov.d[R0 + mainOffset], R7 477 | mov.q R0, [MAIN + mainOffset]"; 478 | 479 | 480 | 481 | 482 | var assembledText = asm.AssembleString(programText).ToString(); 483 | 484 | } 485 | 486 | [Test] 487 | public void TestIfStatement() 488 | { 489 | var programText = @".if.b [MAIN + 0x123] == 0x3 490 | mov r0, 0x3 491 | .else 492 | mov r0, 0x1 493 | .fi 494 | .if.b R0 == 0x1 495 | mov r0, 0x3 496 | .fi 497 | .if.b R0 == R2 498 | mov r0, 0x3 499 | .fi 500 | .if.b R0 == [MAIN + 0x123] 501 | mov r0, 0x3 502 | .fi 503 | .if.b R0 == [MAIN + R2] 504 | mov r0, 0x3 505 | .fi 506 | .if.b R0 == [R2] 507 | mov r0, 0x3 508 | .fi 509 | .if.b R0 == [R2 + R3] 510 | mov r0, 0x3 511 | .fi 512 | .if.b R0 == [R2 + 0x123] 513 | mov r0, 0x3 514 | .fi 515 | 516 | .if.b R0 == [MAIN] 517 | mov r0, 0x3 518 | .fi"; 519 | 520 | 521 | var assembledText = asm.AssembleString(programText).ToString(); 522 | } 523 | } 524 | 525 | } -------------------------------------------------------------------------------- /CheatASMTests/CheatASMTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /CheatASMTests/DisassemblerTests.cs: -------------------------------------------------------------------------------- 1 | using CheatASM; 2 | using NUnit.Framework; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace CheatASMTests 10 | { 11 | class DisassemblerTests 12 | { 13 | Disassembler disasm; 14 | [SetUp] 15 | public void Setup() 16 | { 17 | disasm = new Disassembler(); 18 | } 19 | [Test] 20 | public void TestOpcode0() 21 | { 22 | /* MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (memType=MEM_TYPE) PLUS_SIGN (regOffset=regRef) (PLUS_SIGN (numOffset=numRef))? RSQUARE COMMA (value=numRef)*/ 23 | /* b w d q */ 24 | /* MAIN HEAP */ 25 | Dictionary instructions = new() 26 | { 27 | { "04020000 00000000 00000123", "mov.d [MAIN + R2], 0x123" }, 28 | { "08020000 00000000 00000000 00000123", "mov.q [MAIN + R2], 0x123" }, 29 | { "04080000 00000007 00000345", "mov.d [MAIN + R8 + 0x7], 0x345" }, 30 | { "02170000 00000000 00000236", "mov.w [HEAP + R7], 0x236" }, 31 | { "01170000 00000009 000000FE", "mov.b [HEAP + R7 + 0x9], 0xFE" }, 32 | }; 33 | 34 | foreach (var kvp in instructions) 35 | { 36 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 37 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 38 | } 39 | } 40 | 41 | [Test] 42 | public void TestOpcode1() 43 | { 44 | /* (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) LSQUARE(memType=MEM_TYPE)PLUS_SIGN(offset=numRef)RSQUARE COMMA (value=numRef); */ 45 | /* gt ge lt le eq ne */ 46 | /* b w d q */ 47 | /* MAIN HEAP */ 48 | Dictionary instructions = new() 49 | { 50 | { "14010000 00000123 00000456", "gt.d [MAIN + 0x123], 0x456" }, 51 | { "11120000 00000123 00000056", "ge.b [HEAP + 0x123], 0x56" }, 52 | { "18030012 3456789A 00000045 64123478", "lt.q [MAIN + 0x123456789A], 0x4564123478" }, 53 | { "14040000 00000123 00000456", "le.d [MAIN + 0x123], 0x456" }, 54 | { "14050000 00000123 00000456", "eq.d [MAIN + 0x123], 0x456" }, 55 | { "14060000 00000123 00000456", "ne.d [MAIN + 0x123], 0x456" }, 56 | }; 57 | 58 | foreach (var kvp in instructions) 59 | { 60 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 61 | disasm.ResetIndent(); 62 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 63 | } 64 | } 65 | 66 | [Test] 67 | public void TestOpcode2() 68 | { 69 | Assert.AreEqual(disasm.DisassembleLine("20000000"), "endcond"); 70 | Assert.AreEqual(disasm.DisassembleLine("21000000"), "else"); 71 | } 72 | 73 | [Test] 74 | public void TestOpcode3() 75 | { 76 | /* LOOP (register=regRef) COMMA(value=numRef) | (endloop=END_LOOP) (register=regRef);*/ 77 | 78 | Dictionary instructions = new() 79 | { 80 | { "30070000 00000006", "loop R7, 0x6" }, 81 | { "31060000", "endloop R6" } 82 | }; 83 | 84 | foreach (var kvp in instructions) 85 | { 86 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 87 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 88 | } 89 | } 90 | 91 | [Test] 92 | public void TestOpcode4() 93 | { 94 | /* MOVE (DOT BIT_WIDTH)? (register=regRef) COMMA (value=numRef) # opCode4 */ 95 | 96 | Dictionary instructions = new() 97 | { 98 | { "40000000 00000000 00000001", "mov.q R0, 0x1" }, 99 | { "40020000 00000000 00000003", "mov.q R2, 0x3" }, 100 | { "40070000 00000000 12345678", "mov.q R7, 0x12345678" }, 101 | { "400A0000 11223344 55667788", "mov.q RA, 0x1122334455667788" } 102 | }; 103 | 104 | foreach (var kvp in instructions) 105 | { 106 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 107 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 108 | } 109 | } 110 | 111 | [Test] 112 | public void TestOpcode5() 113 | { 114 | /* MOVE DOT (bitWidth=BIT_WIDTH) (register=regRef) COMMA LSQUARE (memType=MEM_TYPE) (PLUS_SIGN (numOffset=numRef))? RSQUARE 115 | MOVE DOT (bitWidth=BIT_WIDTH) (register=regRef) COMMA LSQUARE (baseRegister=regRef) (PLUS_SIGN (numOffset=numRef))? RSQUARE */ 116 | Dictionary instructions = new() 117 | { 118 | { "51000000 00000012", "mov.b R0, [MAIN + 0x12]" }, 119 | { "54000000 00000000", "mov.d R0, [MAIN]" }, 120 | { "52000000 00000000", "mov.w R0, [MAIN]" }, 121 | { "58100011 22334455", "mov.q R0, [HEAP + 0x1122334455]" }, 122 | { "51001000 00000012", "mov.b R0, [R0 + 0x12]" }, 123 | { "54001000 00000000", "mov.d R0, [R0]" }, 124 | { "52001000 00000000", "mov.w R0, [R0]" }, 125 | 126 | }; 127 | 128 | foreach (var kvp in instructions) 129 | { 130 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 131 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 132 | } 133 | 134 | } 135 | 136 | [Test] 137 | public void TestOpcode6() 138 | { 139 | /* MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (base=regRef) (PLUS_SIGN (regOffset=regRef))? RSQUARE COMMA (value=numRef) (increment=INCREMENT)? */ 140 | Dictionary instructions = new() 141 | { 142 | { "61070000 00000000 00000012", "mov.b [R7], 0x12" }, 143 | { "62041000 00000000 00000012", "mov.w [R4], 0x12 inc" }, 144 | { "64030120 00000000 00000012", "mov.d [R3 + R2], 0x12" }, 145 | { "680B0120 11223344 55667788", "mov.q [RB + R2], 0x1122334455667788" }, 146 | }; 147 | 148 | foreach (var kvp in instructions) 149 | { 150 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 151 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 152 | } 153 | } 154 | 155 | [Test] 156 | public void TestOpcode7() 157 | { 158 | /* (func=LEGACY_ARITHMETIC) DOT (bitWidth=BIT_WIDTH) (register=regRef) COMMA (value=numRef); */ 159 | /* add sub mul lsh rsh */ 160 | Dictionary instructions = new() 161 | { 162 | { "71070000 00000012", "add.b R7, 0x12" }, 163 | { "72041000 00000012", "sub.w R4, 0x12" }, 164 | { "74032000 00000012", "mul.d R3, 0x12" }, 165 | { "780B3000 11223344", "lsh.q RB, 0x11223344" }, 166 | { "74044000 55667788", "rsh.d R4, 0x55667788" }, 167 | }; 168 | 169 | 170 | foreach (var kvp in instructions) 171 | { 172 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 173 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 174 | } 175 | } 176 | 177 | [Test] 178 | public void TestOpcode8() 179 | { 180 | foreach (var val in Enum.GetNames(typeof(KeyMask))) 181 | { 182 | var enumVal = (uint)(Enum.Parse(typeof(KeyMask), val)); 183 | Assert.AreEqual(disasm.DisassembleLine($"8{enumVal.ToString("X7")}"), $"keycheck {val}"); 184 | disasm.ResetIndent(); 185 | } 186 | } 187 | 188 | [Test] 189 | public void TestOpcode9() 190 | { 191 | /* (func=ARITHMETIC) DOT (bitWidth=BIT_WIDTH) (dest=regRef) COMMA (leftReg=regRef) COMMA (right=anyRef); */ 192 | 193 | Dictionary instructions = new() 194 | { 195 | { "91073100 00000012", "add.b R7, R3, 0x12" }, 196 | { "92141100 00000012", "sub.w R4, R1, 0x12" }, 197 | { "94230100 00000012", "mul.d R3, R0, 0x12" }, 198 | { "983BD100 00001122 33445566", "lsh.q RB, RD, 0x112233445566" }, 199 | { "94447100 55667788", "rsh.d R4, R7, 0x55667788" }, 200 | { "91573100 00000012", "and.b R7, R3, 0x12" }, 201 | { "91673100 00000012", "or.b R7, R3, 0x12" }, 202 | { "91873100 00000012", "xor.b R7, R3, 0x12" }, 203 | { "91073020", "add.b R7, R3, R2" }, 204 | { "92141020", "sub.w R4, R1, R2" }, 205 | { "94230070", "mul.d R3, R0, R7" }, 206 | { "983BD080", "lsh.q RB, RD, R8" }, 207 | { "94447010", "rsh.d R4, R7, R1" }, 208 | { "91573000", "and.b R7, R3, R0" }, 209 | { "91673040", "or.b R7, R3, R4" }, 210 | { "91873060", "xor.b R7, R3, R6" }, 211 | { "91773000", "not.b R7, R3" }, 212 | { "91973000", "copy.b R7, R3" }, 213 | }; 214 | 215 | foreach (var kvp in instructions) 216 | { 217 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 218 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 219 | } 220 | } 221 | 222 | [Test] 223 | public void TestOpcodeA() 224 | { 225 | /* 226 | MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (base=regRef) (PLUS_SIGN (regOffset=regRef))? RSQUARE COMMA (regValue=regRef) (increment=INCREMENT)? #opCodeA 227 | MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (base=regRef) (PLUS_SIGN (numOffset=numRef))? RSQUARE COMMA (regValue=regRef) (increment=INCREMENT)? #opCodeA 228 | MOVE DOT (bitWidth=BIT_WIDTH) LSQUARE (memType=MEM_TYPE) (PLUS_SIGN (regOffset=regRef))? (PLUS_SIGN (numOffset=numRef))? RSQUARE COMMA (regValue=regRef) #opCodeA; 229 | */ 230 | Dictionary instructions = new() 231 | { 232 | { "A4100000", "mov.d [R0], R1" }, 233 | { "A4291000", "mov.d [R9], R2 inc" }, 234 | { "A43A0110", "mov.d [RA + R1], R3" }, 235 | { "A44B1110", "mov.d [RB + R1], R4 inc" }, 236 | { "A45C0200 00000123", "mov.d [RC + 0x123], R5" }, 237 | { "A46D1200 00000123", "mov.d [RD + 0x123], R6 inc" }, 238 | { "A47E0300", "mov.d [MAIN + RE], R7" }, 239 | { "A4700400 00000123", "mov.d [MAIN + 0x123], R7" }, 240 | { "A48F0511 23456789", "mov.d [HEAP + RF + 0x123456789], R8" } 241 | }; 242 | 243 | foreach (var kvp in instructions) 244 | { 245 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 246 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 247 | } 248 | 249 | } 250 | 251 | [Test] 252 | public void TestOpcodeC0() 253 | { 254 | /* 255 | (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) (source=regRef) COMMA LSQUARE (memType=MEM_TYPE) PLUS_SIGN (offset=anyRef) RSQUARE 256 | (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) (source=regRef) COMMA LSQUARE (addrReg=regRef) (PLUS_SIGN (offset=anyRef))? RSQUARE 257 | (cond=CONDITIONAL) DOT (bitWidth=BIT_WIDTH) (source=regRef) COMMA (value=anyRef); 258 | */ 259 | 260 | Dictionary instructions = new() 261 | { 262 | { "C01_0510", ".b R0, R1" }, 263 | { "C02_0400 00000123", ".w R0, 0x123" }, 264 | { "C04_0210 00000000", ".d R0, [R1]" }, 265 | { "C08_0210 00000123", ".q R0, [R1 + 0x123]" }, 266 | { "C01_0312", ".b R0, [R1 + R2]" }, 267 | { "C02_0000 00000123", ".w R0, [MAIN + 0x123]" }, 268 | { "C04_0010 00000123", ".d R0, [HEAP + 0x123]" }, 269 | { "C08_0000 00000000", ".q R0, [MAIN]" }, 270 | { "C01_0100", ".b R0, [MAIN + R0]" }, 271 | 272 | }; 273 | string[] comparisons = new string[] { "gt", "ge", "lt", "le", "eq", "ne" }; 274 | foreach (var kvp in instructions) 275 | { 276 | foreach (var comp in comparisons) 277 | { 278 | 279 | var testAnswer = kvp.Key; 280 | testAnswer = testAnswer.Replace('_', ((uint)(Enum.Parse(typeof(ConditionalComparisonType), comp))).ToString()[0]); 281 | 282 | var testInstruction = kvp.Value; 283 | testInstruction = $"{comp}{testInstruction}"; 284 | 285 | 286 | var disassembledInstruction = disasm.DisassembleLine(testAnswer); 287 | Assert.AreEqual(disassembledInstruction, testInstruction, $"Disassembly of '{testAnswer}' gave '{disassembledInstruction}' instead of '{testInstruction}'"); 288 | disasm.ResetIndent(); 289 | } 290 | } 291 | } 292 | 293 | [Test] 294 | public void TestOpcodeC1() 295 | { 296 | /* 297 | (func=SAVE) DOT (type=REG) (index=numRef) COMMA (reg=regRef) 298 | | (func=LOAD) DOT (type=REG) (reg=regRef) COMMA (index=numRef) 299 | | (func=CLEAR) DOT (type=REG) (reg=regRef) 300 | | (func=CLEAR) DOT (type=SAVED) (index=numRef) 301 | */ 302 | 303 | Dictionary instructions = new() 304 | { 305 | { "C1010310", "save.reg 0x1, R3" }, 306 | { "C1030100", "load.reg R3, 0x1" }, 307 | { "C1030030", "clear.reg R3" }, 308 | { "C1010020", "clear.saved 0x1" }, 309 | }; 310 | 311 | foreach (var kvp in instructions) 312 | { 313 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 314 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 315 | } 316 | } 317 | 318 | [Test] 319 | public void TestOpcodeC2() 320 | { 321 | /* 322 | (func=SAVE) DOT (type=REG) (index=numRef) COMMA (reg=regRef) 323 | | (func=LOAD) DOT (type=REG) (reg=regRef) COMMA (index=numRef) 324 | | (func=CLEAR) DOT (type=REG) (reg=regRef) 325 | | (func=CLEAR) DOT (type=SAVED) (index=numRef) 326 | */ 327 | 328 | Dictionary instructions = new() 329 | { 330 | { "C2100002", "save.regs R1" }, 331 | { "C2100006", "save.regs R1, R2" }, 332 | { "C210FFFF", "save.regs R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, RA, RB, RC, RD, RE, RF" }, 333 | 334 | { "C2000002", "load.regs R1" }, 335 | { "C2000006", "load.regs R1, R2" }, 336 | { "C200FFFF", "load.regs R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, RA, RB, RC, RD, RE, RF" }, 337 | 338 | { "C2300002", "clear.regs R1" }, 339 | { "C2300006", "clear.regs R1, R2" }, 340 | { "C230FFFF", "clear.regs R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, RA, RB, RC, RD, RE, RF" }, 341 | 342 | 343 | { "C220001E", "clear.saved 0x1, 0x2, 0x3, 0x4" }, 344 | }; 345 | 346 | foreach (var kvp in instructions) 347 | { 348 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 349 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 350 | } 351 | } 352 | 353 | [Test] 354 | public void TestOpcodeC3() 355 | { 356 | /* 357 | (func=SAVE) DOT (type=STATIC) (sreg=SREGISTER) COMMA (reg=regRef) 358 | (func=LOAD) DOT (type=STATIC) (reg=regRef) COMMA (sreg=SREGISTER); 359 | */ 360 | 361 | Dictionary instructions = new() 362 | { 363 | { "C3000810", "save.static SR1, R0" }, 364 | { "C3000070", "load.static R0, SR7" }, 365 | { "C3000FF1", "save.static SR7F, R1" }, 366 | { "C30007F6", "load.static R6, SR7F" }, 367 | 368 | }; 369 | 370 | foreach (var kvp in instructions) 371 | { 372 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 373 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 374 | } 375 | } 376 | 377 | [Test] 378 | public void TestOpcodeFF0() 379 | { 380 | /* opCodeFF0: (func=PAUSE); */ 381 | Assert.AreEqual(disasm.DisassembleLine("FF000000"), "pause"); 382 | } 383 | 384 | [Test] 385 | public void TestOpcodeFF1() 386 | { 387 | /* opCodeFF1: (func=RESUME); */ 388 | Assert.AreEqual(disasm.DisassembleLine("FF100000"), "resume"); 389 | } 390 | 391 | [Test] 392 | public void TestOpcodeFFF() 393 | { 394 | /* 395 | opCodeFFF: (func=LOG) DOT (bitWidth=BIT_WIDTH) (id=HEX_NUMBER) COMMA LSQUARE (memType=MEM_TYPE) (PLUS_SIGN (offset=anyRef))? RSQUARE 396 | (func=LOG) DOT (bitWidth=BIT_WIDTH) (id=HEX_NUMBER) COMMA LSQUARE (addrReg=regRef) (PLUS_SIGN (offset=anyRef))? RSQUARE 397 | (func=LOG) DOT (bitWidth=BIT_WIDTH) (id=HEX_NUMBER) COMMA (value=regRef); 398 | */ 399 | 400 | Dictionary instructions = new() 401 | { 402 | { "FFF11000 00000000", "log.b 0x1, [MAIN]" }, 403 | { "FFF21000 00000123", "log.w 0x1, [MAIN + 0x123]" }, 404 | { "FFF41103", "log.d 0x1, [MAIN + R3]" }, 405 | { "FFF81010 00000123", "log.q 0x1, [HEAP + 0x123]" }, 406 | { "FFF11220 00000000", "log.b 0x1, [R2]" }, 407 | { "FFF11323", "log.b 0x1, [R2 + R3]" }, 408 | { "FFF21240 00000123", "log.w 0x1, [R4 + 0x123]" }, 409 | { "FFF41470", "log.d 0x1, R7" }, 410 | }; 411 | 412 | foreach (var kvp in instructions) 413 | { 414 | var disassembledInstruction = disasm.DisassembleLine(kvp.Key); 415 | Assert.AreEqual(disassembledInstruction, kvp.Value, $"Disassembly of '{kvp.Key}' gave '{disassembledInstruction}' instead of '{kvp.Value}'"); 416 | } 417 | } 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CheatASM 2 | 3 | CheatASM is a utility to designed to enable more readable cheatcode development for Atmosphere Cheat Codes. CheatASM provides an assembly-like syntax/language and compiles down into the proper cheatcode format used in the Atmosphere CFW. It can also act as a disassembler, converting cheatcodes back into a more readable syntax. 4 | 5 | The project is not under active development but as new cheat opcodes are added to Atmosphere, the assembly syntax will be updated to support them. 6 | 7 | ### Prerequisites 8 | 9 | In order to run the software your machine must have the dotnetcore 2.2+ runtime installed. To build the project you need the have the SDK installed instead. 10 | 11 | .NET Core downloads can be found here: (https://dotnet.microsoft.com/download/dotnet-core/2.2) 12 | 13 | ### Installing 14 | 15 | If running from an official release, simply extract the archive for your architecture and run like any other commmand line utility. 16 | 17 | For running from source, see the "Building" section below 18 | 19 | ## Example 20 | 21 | ### Disassembly output 22 | 23 | ``` 24 | 11130000 56C04A6C 0000000A 25 | 01100000 56C04A6C 0000000A 26 | 20000000 27 | ``` 28 | 29 | becomes 30 | 31 | ``` 32 | lt.b [HEAP+0x56C04A6C], 0xA 33 | mov.b [HEAP+R0+0x56C04A6C], 0xA 34 | endcond 35 | ``` 36 | 37 | ## Usage 38 | Here are some useful examples of how to use this utility (please see -h for a full listing of command line arguments) 39 | 40 | CheatASM has 2 overall modes -d for disassembly and -a for assembly, you cannot specify both flags at the same time. 41 | 42 | Additionally CheatASM provides a REPL using the `--repl` flag in which you can test various things. the REPL accepts input until a blank line is sent, then it tries to determine if what was entered is an assembled code (and thus needs to be disassembled) or if it was assembly syntax (and thus needs to be assembled). 43 | 44 | #### Disassemble File to Standard Out 45 | If no -o flag is provided CheatASM will output the result to stdout. 46 | 47 | `CheatASM -d -i 03fd1524e17a841c.txt` 48 | 49 | #### Disassemble File to File 50 | By providing an -o flag, the output will be writen to file 51 | 52 | `CheatASM -d -i 03fd1524e17a841c.txt -o 03fd1524e17a841c_asm.txt` 53 | 54 | #### Assembling File to File 55 | `CheatASM -a -i 03fd1524e17a841c_asm.txt -o 03fd1524e17a841c.txt` 56 | 57 | #### Disassemble Directory Recursively 58 | When specifying a directory for the -i flag a corresponding -o flag MUST be provided. 59 | If the -o flag specifies the same folder as -i the files will be overwritten. 60 | 61 | ` 62 | CheatASM -d -r -i CheatsFolder -o CheatsDisassembled 63 | ` 64 | 65 | #### Disassemble Opcode Directly 66 | CheatASM also allows for the dis/assembly of a opcode or assembly instruction directly using the -t parameter. If -o is not specified the result will be written to stdout, otherwise to the file specified by the -o parameter. 67 | 68 | ` 69 | CheatASM -d -t "04010000 006C7634 0098967F" 70 | ` 71 | 72 | ` 73 | CheatASM -a -t "mov.d [MAIN+R1+0x6C7634], 0x98967F" 74 | ` 75 | 76 | ## Assembly Mnemonics 77 | 78 | The desire is for the assembly mnemonics to not be something you need to memorize or reference often. The aim is to be as intuitive as possible (assuming you have assembly exposure) and just let you write what you need. There are some general patterns that hte mnemonics follow: 79 | 80 | `command.bitwidth param1, param2` 81 | 82 | Command can be something like "mov" or "add", BitWidth is one of the following: "b", "w", "d", "q" for Byte, Word, Dword, Qword respectively. 83 | 84 | When using the more complex addressing modes the order that items should be specified is always `MemoryType -> Register -> Literal value`. 85 | 86 | ### Variables 87 | 88 | Cheat ASM allows for you to define mutable variables as well as constant variables. During assembly constant variables will have their literal value substituted, while mutable variables will be assigned a register number. The assigned register number if effectively reserved for use and the assembler will error in the event of an explicit use of a register number that is assigned to a variable. 89 | 90 | Register assignment for variables happens per-cheat and they are assigned starting of 0xF and descending. Assignment is done at first-use. 91 | 92 | Variables can be of the following types: `.u8, .s8,.u16, .s16, .u32, .s32, .u64, .s64, .f32, .f64` where `u` is unsigned, `s` is signed, and `f` is floating point. The number represents the bit width of the value. 93 | 94 | Mutable variables will have an initial opcode inserted at the beginning of each cheat to initialize the register with the proper value. This means that at the start of each individual cheat the mutable variable initial value gets restored. 95 | 96 | Here is a concrete example of how to use variables with CheatASM: 97 | 98 | ``` 99 | floatTest: .f32 4.83 100 | mainOffset: .u32 const 0x1234 101 | coinOffset: .u32 const 0x12 102 | ten: .u32 0xA 103 | 104 | .cheat master "Setup" 105 | mov.d[R0 + 0x123], floatTest 106 | mov.q R0, [MAIN + mainOffset] 107 | 108 | .cheat "Sample" 109 | mov.q [MAIN + 0x432], ten 110 | mov.d[R0 + mainOffset], R7 111 | mov.q R0, [MAIN + mainOffset] 112 | mov.d[R0 + 0x123], floatTest 113 | ``` 114 | 115 | Assembles to: 116 | 117 | ``` 118 | [Assembled by CheatASM] 119 | {Setup} 120 | 400F0000 00000000 409A8F5C 121 | A4F00200 00000123 122 | 58000000 00001234 123 | 124 | [Sample] 125 | 400F0000 00000000 0000000A 126 | 400E0000 00000000 409A8F5C 127 | A8F00400 00000432 128 | A4700200 00001234 129 | 58000000 00001234 130 | A4E00200 00000123 131 | ``` 132 | 133 | Which disassembles to: 134 | 135 | ``` 136 | [Assembled by CheatASM] 137 | {Setup} 138 | mov.q RF, 0x409A8F5C 139 | mov.d [R0 + 0x123], RF 140 | mov.q R0, [MAIN + 0x1234] 141 | 142 | [Sample] 143 | mov.q RF, 0xA 144 | mov.q RE, 0x409A8F5C 145 | mov.q [MAIN + 0x432], RF 146 | mov.d [R0 + 0x1234], R7 147 | mov.q R0, [MAIN + 0x1234] 148 | mov.d [R0 + 0x123], RE 149 | ``` 150 | 151 | ### OpCode Mnemonic Listing 152 | 153 | Below you will find examples of every mnemonic supported by CheatASM. To find out what each opcode does, please reference the Atmosphere Cheat docs. 154 | 155 | #### Opcode 0 (Store Static) 156 | 157 | ``` 158 | mov.d [MAIN + R2], 0x123 159 | mov.q [MAIN + R2], 0x123 160 | mov.d [MAIN + R8 + 0x7], 0x345 161 | mov.w [HEAP + R7], 0x236 162 | mov.b [HEAP + R7 + 0x9], 0xFE" 163 | ``` 164 | 165 | #### Opcode 1 (Conditional) 166 | 167 | Compares the byte at address and enters the conditional block if it is less than value. 168 | 169 | ``` 170 | gt.d [MAIN + 0x123], 0x456 171 | ge.b [HEAP + 0x123], 0x56 172 | lt.q [MAIN + 0x123456789A], 0x4564123478 173 | le.d [MAIN + 0x123], 0x456 174 | eq.d [MAIN + 0x123], 0x456 175 | ne.d [MAIN + 0x123], 0x456 176 | ... 177 | endcond 178 | ``` 179 | 180 | #### Opcode 2 (End Conditional) 181 | 182 | Terminates a conditional block 183 | 184 | ``` 185 | endcond 186 | ``` 187 | 188 | #### Opcode 3 (Begin/End Loop) 189 | 190 | ``` 191 | loop R0, 0x23 192 | ... 193 | endloop R0 194 | ``` 195 | 196 | #### Opcode 4 (Static to Register) 197 | 198 | Moves a static value into a register (bitwidth is always q) 199 | 200 | ``` 201 | mov.b R0, 0x1 202 | mov R2, 0x3 203 | mov.d R7, 0x12345678 204 | mov.q RA, 0x1122334455667788 205 | ``` 206 | 207 | #### Opcode 5 (Memory to Register) 208 | 209 | Move a value from memory into a register 210 | 211 | ``` 212 | mov.b R0, [MAIN + 0x12] 213 | mov.d R0, [MAIN + 0x0] 214 | mov.w R0, [MAIN] 215 | mov.q R0, [HEAP + 0x1122334455] 216 | mov.b R0, [R0 + 0x12] 217 | mov.d R0, [R0 + 0x0] 218 | mov.w R0, [R0] 219 | ``` 220 | 221 | #### Opcode 6 (Static to Address) 222 | 223 | ``` 224 | mov.b [R7], 0x12 225 | mov.w [R4], 0x12 inc 226 | mov.d [R3 + R2], 0x12 227 | mov.q [RB + R2], 0x1122334455667788 228 | ``` 229 | 230 | #### Opcode 7 (Legacy Arithmetic) 231 | 232 | Supported arithmetic methods are `add, sub, mul, lsh, rsh` 233 | 234 | ``` 235 | add.b R7, 0x12 236 | sub.w R4, 0x12 237 | mul.d R3, 0x12 238 | lsh.q RB, 0x11223344 239 | rsh.d R4, 0x55667788 240 | ``` 241 | 242 | #### Opcode 8 (KeyCheck) 243 | 244 | Supported keys are: `A, B, X, Y, LSP, RSP, L, R, ZL, ZR, PLUS, MINUS, LEFT, UP, RIGHT, DOWN, LSL, LSU, LSR, LSD, RSL, RSU, RSR,RSD, SL, and SR` 245 | 246 | ``` 247 | keycheck 248 | ``` 249 | 250 | 251 | #### Opcode 9 (Arithmetic) 252 | 253 | Supported arithmetic methods are `add, sub, mul, lsh, rsh, and, or, not, xor, none` 254 | 255 | ``` 256 | add.b R7, R3, 0x12 257 | sub.w R4, R1, 0x12 258 | mul.d R3, R0, 0x12 259 | lsh.q RB, RD, 0x112233445566 260 | rsh.d R4, R7, 0x55667788 261 | and.b R7, R3, 0x12 262 | or.b R7, R3, 0x12 263 | xor.b R7, R3, 0x12 264 | add.b R7, R3, R2 265 | sub.w R4, R1, R2 266 | mul.d R3, R0, R7 267 | lsh.q RB, RD, R8 268 | rsh.d R4, R7, R1 269 | and.b R7, R3, R0 270 | or.b R7, R3, R4 271 | xor.b R7, R3, R6 272 | not.b R7, R3 273 | copy.b R7, R3 274 | ``` 275 | 276 | #### Opcode 10 (Register to Address) 277 | 278 | ``` 279 | mov.d [R0], R1 280 | mov.d [R9], R2 inc 281 | mov.d [RA+ R1], R3 282 | mov.d [RB + R1], R4 inc 283 | mov.d [RC + 0x123], R5 284 | mov.d [RD + 0x123], R6 inc 285 | mov.d [MAIN + RE], R7 286 | mov.d [MAIN + 0x123], R7 287 | mov.d [HEAP + RF + 0x123456789], R8 288 | ``` 289 | 290 | #### Extended Opcode 0xC0 (Register Conditional) 291 | 292 | ``` 293 | gt.b R0, R1 294 | ge.w R0, 0x123 295 | lt.d R0, [R1] 296 | le.q R0, [R1 + 0x123] 297 | eq.b R0, [R1 + R2] 298 | ne.w R0, [MAIN + 0x123] 299 | gt.d R0, [HEAP + 0x123] 300 | ge.q R0, [MAIN] 301 | lt.b R0, [MAIN + R0] 302 | ``` 303 | 304 | #### Save/Load/Clear Register 305 | ``` 306 | save.reg 0x1, R3 307 | load.reg R3, 0x1 308 | clear.reg R3 309 | clear.saved 0x1 310 | ``` 311 | 312 | #### Save/Load/Clear Registers 313 | ``` 314 | save.regs R1 315 | save.regs R1,R2 316 | save.regs R0, R1,R2,R3,R4,R5,R6,R7,R8,R9,RA,RB,RC,RD,RE,RF 317 | 318 | load.regs R1 319 | load.regs R1,R2 320 | load.regs R0,R1,R2,R3,R4,R5,R6,R7,R8,R9,RA,RB,RC,RD,RE,RF 321 | 322 | clear.regs R1 323 | clear.regs R1,R2 324 | clear.regs R0,R1,R2,R3,R4,R5,R6,R7,R8,R9,RA,RB,RC,RD,RE,RF 325 | ``` 326 | 327 | #### Save/Load/Clear Static Register 328 | 329 | Static registers are referenced by SR\ 330 | 331 | ``` 332 | save.static SR1, R0 333 | load.static R0, SR7 334 | save.static SR7F, R1 335 | load.static R6, SR7F 336 | ``` 337 | 338 | #### Pause Process 339 | ``` 340 | pause 341 | ``` 342 | 343 | #### Resume Process 344 | ``` 345 | resume 346 | ``` 347 | 348 | #### Debug Log 349 | 350 | Format is `log. 0xLogID, value` 351 | 352 | ``` 353 | log.b 0x1, [MAIN] 354 | log.w 0x1, [MAIN + 0x123] 355 | log.d 0x1, [MAIN + R3] 356 | log.q 0x1, [HEAP + 0x123] 357 | log.b 0x1, [R2] 358 | log.b 0x1, [R2 + R3] 359 | log.w 0x1, [R4 + 0x123] 360 | log.d 0x1, R7 361 | ``` 362 | 363 | ## High Level Mnemonics 364 | 365 | CheatASM currently supports If/Else has a higher level mnemonic, this assembles into the proper "conditional" and "end conditional" opcodes. 366 | 367 | Supported conditional operators are `>, >=, <, <=, ==, !=` 368 | 369 | Example: 370 | 371 | ``` 372 | .if.b [MAIN + 0x123] == 0x3 373 | mov r0, 0x3 374 | .else 375 | mov r0, 0x1 376 | .fi 377 | .if.b R0 == 0x1 378 | mov r0, 0x3 379 | .fi 380 | .if.b R0 == R2 381 | mov r0, 0x3 382 | .fi 383 | .if.b R0 == [MAIN + 0x123] 384 | mov r0, 0x3 385 | .fi 386 | .if.b R0 == [MAIN + R2] 387 | mov r0, 0x3 388 | .fi 389 | .if.b R0 == [R2] 390 | mov r0, 0x3 391 | .fi 392 | .if.b R0 == [R2 + R3] 393 | mov r0, 0x3 394 | .fi 395 | .if.b R0 == [R2 + 0x123] 396 | mov r0, 0x3 397 | .fi 398 | 399 | .if.b R0 == [MAIN] 400 | mov r0, 0x3 401 | .fi 402 | ``` 403 | 404 | Assembles to: 405 | 406 | ``` 407 | 11050000 00000123 00000003 408 | 40000000 00000000 00000003 409 | 21000000 410 | 40000000 00000000 00000001 411 | 20000000 412 | C0150400 00000001 413 | 40000000 00000000 00000003 414 | 20000000 415 | C0150520 416 | 40000000 00000000 00000003 417 | 20000000 418 | C0150000 00000123 419 | 40000000 00000000 00000003 420 | 20000000 421 | C0150102 422 | 40000000 00000000 00000003 423 | 20000000 424 | C0150220 00000000 425 | 40000000 00000000 00000003 426 | 20000000 427 | C0150323 428 | 40000000 00000000 00000003 429 | 20000000 430 | C0150220 00000123 431 | 40000000 00000000 00000003 432 | 20000000 433 | C0150000 00000000 434 | 40000000 00000000 00000003 435 | 20000000 436 | ``` 437 | 438 | Which disassembles to: 439 | 440 | ``` 441 | eq.b [MAIN + 0x123], 0x3 442 | mov.q R0, 0x3 443 | else 444 | mov.q R0, 0x1 445 | endcond 446 | eq.b R0, 0x1 447 | mov.q R0, 0x3 448 | endcond 449 | eq.b R0, R2 450 | mov.q R0, 0x3 451 | endcond 452 | eq.b R0, [MAIN + 0x123] 453 | mov.q R0, 0x3 454 | endcond 455 | eq.b R0, [MAIN + R2] 456 | mov.q R0, 0x3 457 | endcond 458 | eq.b R0, [R2] 459 | mov.q R0, 0x3 460 | endcond 461 | eq.b R0, [R2 + R3] 462 | mov.q R0, 0x3 463 | endcond 464 | eq.b R0, [R2 + 0x123] 465 | mov.q R0, 0x3 466 | endcond 467 | eq.b R0, [MAIN] 468 | mov.q R0, 0x3 469 | endcond 470 | ``` 471 | 472 | ## Building From Source 473 | 474 | ### Ubuntu 18.04 475 | #### Install Prerequisites 476 | Instructions taken from (https://dotnet.microsoft.com/download/linux-package-manager/ubuntu18-04/sdk-2.2.104) 477 | ``` 478 | $ wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb 479 | $ sudo dpkg -i packages-microsoft-prod.deb 480 | 481 | $ sudo add-apt-repository universe 482 | $ sudo apt-get install apt-transport-https 483 | $ sudo apt-get update 484 | $ sudo apt-get install dotnet-sdk-2.2 485 | ``` 486 | #### Clone Repository and Build 487 | ``` 488 | $ git clone https://github.com/tslater2006/CheatASM.git 489 | $ cd CheatASM 490 | $ dotnet publish -c Release -r linux-x64 --self-contained false 491 | $ cd CheatASM/bin/Release/netcoreapp2.2/linux-x64/publish/ 492 | $ ./CheatASM -h 493 | ``` 494 | 495 | ## License 496 | 497 | This project is licensed under the GNU Public License v2 License - see the [LICENSE.md](LICENSE.md) file for details 498 | 499 | -------------------------------------------------------------------------------- /publish.bat: -------------------------------------------------------------------------------- 1 | dotnet publish -c Release -r linux-x64 --self-contained false 2 | dotnet publish -c Release -r win10-x64 --self-contained false 3 | dotnet publish -c Release -r osx-x64 --self-contained false 4 | --------------------------------------------------------------------------------