mirror of
https://github.com/ApfelTeeSaft/NESDecompiler.git
synced 2026-08-26 19:33:30 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
+398
@@ -0,0 +1,398 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 ApfelTeeSaft
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NESDecompiler.Core\NESDecompiler.Core.csproj" />
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,214 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using CommandLine;
|
||||
using NESDecompiler.Core.ROM;
|
||||
using NESDecompiler.Core.Disassembly;
|
||||
using NESDecompiler.Core.Decompilation;
|
||||
using NESDecompiler.Core.Exceptions;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NESDecompiler.CLI
|
||||
{
|
||||
/// <summary>
|
||||
/// Command-line options for the decompiler
|
||||
/// </summary>
|
||||
class Options
|
||||
{
|
||||
[Option('i', "input", Required = true, HelpText = "Input NES ROM file path")]
|
||||
public string InputFile { get; set; } = string.Empty;
|
||||
|
||||
[Option('o', "output", Required = false, HelpText = "Output directory for generated files")]
|
||||
public string? OutputDirectory { get; set; }
|
||||
|
||||
[Option('d', "disassemble", Required = false, Default = false, HelpText = "Generate disassembly output")]
|
||||
public bool GenerateDisassembly { get; set; }
|
||||
|
||||
[Option('c', "decompile", Required = false, Default = true, HelpText = "Generate C code output")]
|
||||
public bool GenerateCCode { get; set; }
|
||||
|
||||
[Option('v', "verbose", Required = false, Default = false, HelpText = "Set output to verbose messages")]
|
||||
public bool Verbose { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main program class for the CLI interface
|
||||
/// </summary>
|
||||
class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Entry point for the CLI application
|
||||
/// </summary>
|
||||
static int Main(string[] args)
|
||||
{
|
||||
return Parser.Default.ParseArguments<Options>(args)
|
||||
.MapResult(
|
||||
options => RunDecompiler(options),
|
||||
errors => 1
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the decompiler with the specified options
|
||||
/// </summary>
|
||||
/// <param name="options">Command-line options</param>
|
||||
/// <returns>0 for success, non-zero for failure</returns>
|
||||
static int RunDecompiler(Options options)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"NES ROM Decompiler");
|
||||
Console.WriteLine($"=================");
|
||||
Console.WriteLine();
|
||||
|
||||
if (!File.Exists(options.InputFile))
|
||||
{
|
||||
Console.Error.WriteLine($"Error: Input file '{options.InputFile}' does not exist");
|
||||
return 1;
|
||||
}
|
||||
|
||||
string outputDirectory = options.OutputDirectory ?? Path.GetDirectoryName(options.InputFile) ?? ".";
|
||||
if (!Directory.Exists(outputDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
}
|
||||
|
||||
Console.WriteLine($"Loading ROM: {options.InputFile}");
|
||||
var romLoader = new ROMLoader();
|
||||
var romInfo = romLoader.LoadFromFile(options.InputFile);
|
||||
|
||||
if (options.Verbose)
|
||||
{
|
||||
Console.WriteLine(romInfo.ToString());
|
||||
}
|
||||
|
||||
byte[] prgRomData = romLoader.GetPRGROMData();
|
||||
|
||||
Console.WriteLine("Disassembling code...");
|
||||
var disassembler = new Disassembler(romInfo, prgRomData);
|
||||
disassembler.Disassemble();
|
||||
|
||||
if (options.Verbose)
|
||||
{
|
||||
Console.WriteLine($"Disassembled {disassembler.Instructions.Count} instructions");
|
||||
}
|
||||
|
||||
if (options.GenerateDisassembly)
|
||||
{
|
||||
string disassemblyFile = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(options.InputFile) + ".asm");
|
||||
Console.WriteLine($"Generating disassembly: {disassemblyFile}");
|
||||
|
||||
string disassembly = disassembler.ToAssemblyString();
|
||||
File.WriteAllText(disassemblyFile, disassembly);
|
||||
}
|
||||
|
||||
if (options.GenerateCCode)
|
||||
{
|
||||
Console.WriteLine("Decompiling to C code...");
|
||||
var decompiler = new Decompiler(romInfo, disassembler);
|
||||
decompiler.Decompile();
|
||||
|
||||
if (options.Verbose)
|
||||
{
|
||||
Console.WriteLine($"Identified {decompiler.Variables.Count} variables and {decompiler.Functions.Count} functions");
|
||||
}
|
||||
|
||||
string cCodeFile = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(options.InputFile) + ".c");
|
||||
Console.WriteLine($"Generating C code: {cCodeFile}");
|
||||
|
||||
string cCode = decompiler.GenerateCCode();
|
||||
File.WriteAllText(cCodeFile, cCode);
|
||||
|
||||
string headerFile = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(options.InputFile) + ".h");
|
||||
Console.WriteLine($"Generating header file: {headerFile}");
|
||||
|
||||
string headerCode = GenerateHeaderFile(decompiler);
|
||||
File.WriteAllText(headerFile, headerCode);
|
||||
}
|
||||
|
||||
Console.WriteLine("Decompilation completed successfully");
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Exception currentEx = ex;
|
||||
Console.Error.WriteLine($"Error: {currentEx.Message}");
|
||||
|
||||
if (options.Verbose)
|
||||
{
|
||||
while (currentEx.InnerException != null)
|
||||
{
|
||||
currentEx = currentEx.InnerException;
|
||||
Console.Error.WriteLine($" Caused by: {currentEx.Message}");
|
||||
}
|
||||
|
||||
Console.Error.WriteLine();
|
||||
Console.Error.WriteLine("Stack trace:");
|
||||
Console.Error.WriteLine(ex.StackTrace);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a C header file for the decompiled ROM
|
||||
/// </summary>
|
||||
/// <param name="decompiler">The decompiler instance</param>
|
||||
/// <returns>The generated header code</returns>
|
||||
static string GenerateHeaderFile(Decompiler decompiler)
|
||||
{
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
writer.WriteLine("/*");
|
||||
writer.WriteLine(" * Decompiled NES ROM");
|
||||
writer.WriteLine($" * ROM: {decompiler.ROMInfo}");
|
||||
writer.WriteLine(" */");
|
||||
writer.WriteLine();
|
||||
|
||||
string guardName = Path.GetFileNameWithoutExtension(decompiler.ROMInfo.RawData[0].ToString()).ToUpper() + "_H";
|
||||
writer.WriteLine($"#ifndef {guardName}");
|
||||
writer.WriteLine($"#define {guardName}");
|
||||
writer.WriteLine();
|
||||
|
||||
writer.WriteLine("#include <stdint.h>");
|
||||
writer.WriteLine("#include <stdbool.h>");
|
||||
writer.WriteLine();
|
||||
|
||||
writer.WriteLine("// NES Hardware Registers");
|
||||
writer.WriteLine("#define PPUCTRL (*((volatile uint8_t*)0x2000))");
|
||||
writer.WriteLine("#define PPUMASK (*((volatile uint8_t*)0x2001))");
|
||||
writer.WriteLine("#define PPUSTATUS (*((volatile uint8_t*)0x2002))");
|
||||
writer.WriteLine("#define OAMADDR (*((volatile uint8_t*)0x2003))");
|
||||
writer.WriteLine("#define OAMDATA (*((volatile uint8_t*)0x2004))");
|
||||
writer.WriteLine("#define PPUSCROLL (*((volatile uint8_t*)0x2005))");
|
||||
writer.WriteLine("#define PPUADDR (*((volatile uint8_t*)0x2006))");
|
||||
writer.WriteLine("#define PPUDATA (*((volatile uint8_t*)0x2007))");
|
||||
writer.WriteLine("#define OAMDMA (*((volatile uint8_t*)0x4014))");
|
||||
writer.WriteLine("#define SND_CHN (*((volatile uint8_t*)0x4015))");
|
||||
writer.WriteLine("#define JOY1 (*((volatile uint8_t*)0x4016))");
|
||||
writer.WriteLine("#define JOY2 (*((volatile uint8_t*)0x4017))");
|
||||
writer.WriteLine();
|
||||
|
||||
writer.WriteLine("// Variables");
|
||||
foreach (var variable in decompiler.Variables.Values)
|
||||
{
|
||||
if (variable.Address < 0x2000 || variable.Address >= 0x8000)
|
||||
{
|
||||
writer.WriteLine($"extern {variable.GetCType()} {variable.Name};");
|
||||
}
|
||||
}
|
||||
writer.WriteLine();
|
||||
|
||||
writer.WriteLine("// Functions");
|
||||
foreach (var function in decompiler.Functions.Values)
|
||||
{
|
||||
writer.WriteLine($"void {function.Name}();");
|
||||
}
|
||||
writer.WriteLine();
|
||||
|
||||
writer.WriteLine($"#endif // {guardName}");
|
||||
|
||||
return writer.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NESDecompiler.Core.CPU
|
||||
{
|
||||
/// <summary>
|
||||
/// Addressing modes for 6502 CPU instructions
|
||||
/// </summary>
|
||||
public enum AddressingMode
|
||||
{
|
||||
Implied, // No operand (e.g., RTS)
|
||||
Accumulator, // Operand is the accumulator (e.g., LSR A)
|
||||
Immediate, // Operand is 8-bit value (e.g., LDA #$12)
|
||||
ZeroPage, // Operand is 8-bit address (e.g., LDA $12)
|
||||
ZeroPageX, // Operand is 8-bit address, X-indexed (e.g., LDA $12,X)
|
||||
ZeroPageY, // Operand is 8-bit address, Y-indexed (e.g., LDX $12,Y)
|
||||
Relative, // Operand is 8-bit signed offset (e.g., BNE $12)
|
||||
Absolute, // Operand is 16-bit address (e.g., LDA $1234)
|
||||
AbsoluteX, // Operand is 16-bit address, X-indexed (e.g., LDA $1234,X)
|
||||
AbsoluteY, // Operand is 16-bit address, Y-indexed (e.g., LDA $1234,Y)
|
||||
Indirect, // Operand is 16-bit address pointing to the address (e.g., JMP ($1234))
|
||||
IndexedIndirect, // Operand is 8-bit address, X-indexed, pointing to 16-bit address (e.g., LDA ($12,X))
|
||||
IndirectIndexed // Operand is 8-bit address pointing to 16-bit address, Y-indexed (e.g., LDA ($12),Y)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instruction type categories
|
||||
/// </summary>
|
||||
public enum InstructionType
|
||||
{
|
||||
Load, // Load operations (LDA, LDX, LDY)
|
||||
Store, // Store operations (STA, STX, STY)
|
||||
Transfer, // Register transfers (TAX, TXA, etc.)
|
||||
Stack, // Stack operations (PHA, PLA, PHP, PLP, TSX, TXS)
|
||||
Arithmetic, // Arithmetic operations (ADC, SBC)
|
||||
Increment, // Increment operations (INC, INX, INY)
|
||||
Decrement, // Decrement operations (DEC, DEX, DEY)
|
||||
Shift, // Shift operations (ASL, LSR, ROL, ROR)
|
||||
Logic, // Logic operations (AND, EOR, ORA)
|
||||
Compare, // Compare operations (CMP, CPX, CPY)
|
||||
Branch, // Branch operations (BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS)
|
||||
Jump, // Jump operations (JMP, JSR)
|
||||
Return, // Return operations (RTS, RTI)
|
||||
Set, // Set flag operations (SEC, SED, SEI)
|
||||
Clear, // Clear flag operations (CLC, CLD, CLI, CLV)
|
||||
Interrupt, // Interrupt operations (BRK)
|
||||
Other // Other operations (NOP, ???)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about a 6502 CPU instruction
|
||||
/// </summary>
|
||||
public class InstructionInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The opcode value (0x00-0xFF)
|
||||
/// </summary>
|
||||
public byte Opcode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The mnemonic for this instruction (e.g., "LDA", "STA")
|
||||
/// </summary>
|
||||
public string Mnemonic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The addressing mode for this instruction
|
||||
/// </summary>
|
||||
public AddressingMode AddressingMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The size of this instruction in bytes (including operands)
|
||||
/// </summary>
|
||||
public byte Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of cycles this instruction takes to execute
|
||||
/// </summary>
|
||||
public byte Cycles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this instruction can take an additional cycle on page boundary crossing
|
||||
/// </summary>
|
||||
public bool ExtraCycleOnPageCross { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The instruction type category
|
||||
/// </summary>
|
||||
public InstructionType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this instruction is a valid 6502 instruction
|
||||
/// </summary>
|
||||
public bool IsValid { get; set; } = true;
|
||||
|
||||
public InstructionInfo(byte opcode, string mnemonic, AddressingMode addressingMode,
|
||||
byte size, byte cycles, bool extraCycleOnPageCross, InstructionType type)
|
||||
{
|
||||
Opcode = opcode;
|
||||
Mnemonic = mnemonic;
|
||||
AddressingMode = addressingMode;
|
||||
Size = size;
|
||||
Cycles = cycles;
|
||||
ExtraCycleOnPageCross = extraCycleOnPageCross;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the operand format for this addressing mode
|
||||
/// </summary>
|
||||
public string GetOperandFormat()
|
||||
{
|
||||
return AddressingMode switch
|
||||
{
|
||||
AddressingMode.Implied => "",
|
||||
AddressingMode.Accumulator => "A",
|
||||
AddressingMode.Immediate => "#$%02X",
|
||||
AddressingMode.ZeroPage => "$%02X",
|
||||
AddressingMode.ZeroPageX => "$%02X,X",
|
||||
AddressingMode.ZeroPageY => "$%02X,Y",
|
||||
AddressingMode.Relative => "$%02X", // Will be processed specially for branches
|
||||
AddressingMode.Absolute => "$%04X",
|
||||
AddressingMode.AbsoluteX => "$%04X,X",
|
||||
AddressingMode.AbsoluteY => "$%04X,Y",
|
||||
AddressingMode.Indirect => "($%04X)",
|
||||
AddressingMode.IndexedIndirect => "($%02X,X)",
|
||||
AddressingMode.IndirectIndexed => "($%02X),Y",
|
||||
_ => "???"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the 6502 CPU instruction set
|
||||
/// </summary>
|
||||
public static class InstructionSet
|
||||
{
|
||||
// The full 6502 instruction set
|
||||
private static readonly Dictionary<byte, InstructionInfo> instructions = new Dictionary<byte, InstructionInfo>();
|
||||
|
||||
// Maps mnemonic + addressing mode to an instruction info object
|
||||
private static readonly Dictionary<(string, AddressingMode), InstructionInfo> mnemonicMap =
|
||||
new Dictionary<(string, AddressingMode), InstructionInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Static constructor to initialize the instruction set
|
||||
/// </summary>
|
||||
static InstructionSet()
|
||||
{
|
||||
InitializeInstructionSet();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instruction by its opcode
|
||||
/// </summary>
|
||||
/// <param name="opcode">The opcode value</param>
|
||||
/// <returns>Information about the instruction</returns>
|
||||
public static InstructionInfo GetInstruction(byte opcode)
|
||||
{
|
||||
if (instructions.TryGetValue(opcode, out var instruction))
|
||||
{
|
||||
return instruction;
|
||||
}
|
||||
|
||||
// Return unknown instruction
|
||||
return new InstructionInfo(
|
||||
opcode, "???", AddressingMode.Implied, 1, 2, false, InstructionType.Other)
|
||||
{
|
||||
IsValid = false
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instruction by its mnemonic and addressing mode
|
||||
/// </summary>
|
||||
/// <param name="mnemonic">The instruction mnemonic</param>
|
||||
/// <param name="addressingMode">The addressing mode</param>
|
||||
/// <returns>Information about the instruction, or null if not found</returns>
|
||||
public static InstructionInfo? GetInstruction(string mnemonic, AddressingMode addressingMode)
|
||||
{
|
||||
var key = (mnemonic.ToUpper(), addressingMode);
|
||||
if (mnemonicMap.TryGetValue(key, out var instruction))
|
||||
{
|
||||
return instruction;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the 6502 instruction set
|
||||
/// </summary>
|
||||
private static void InitializeInstructionSet()
|
||||
{
|
||||
// Load/Store Operations
|
||||
Add(0xA9, "LDA", AddressingMode.Immediate, 2, 2, false, InstructionType.Load);
|
||||
Add(0xA5, "LDA", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Load);
|
||||
Add(0xB5, "LDA", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Load);
|
||||
Add(0xAD, "LDA", AddressingMode.Absolute, 3, 4, false, InstructionType.Load);
|
||||
Add(0xBD, "LDA", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Load);
|
||||
Add(0xB9, "LDA", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Load);
|
||||
Add(0xA1, "LDA", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Load);
|
||||
Add(0xB1, "LDA", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Load);
|
||||
|
||||
Add(0xA2, "LDX", AddressingMode.Immediate, 2, 2, false, InstructionType.Load);
|
||||
Add(0xA6, "LDX", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Load);
|
||||
Add(0xB6, "LDX", AddressingMode.ZeroPageY, 2, 4, false, InstructionType.Load);
|
||||
Add(0xAE, "LDX", AddressingMode.Absolute, 3, 4, false, InstructionType.Load);
|
||||
Add(0xBE, "LDX", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Load);
|
||||
|
||||
Add(0xA0, "LDY", AddressingMode.Immediate, 2, 2, false, InstructionType.Load);
|
||||
Add(0xA4, "LDY", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Load);
|
||||
Add(0xB4, "LDY", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Load);
|
||||
Add(0xAC, "LDY", AddressingMode.Absolute, 3, 4, false, InstructionType.Load);
|
||||
Add(0xBC, "LDY", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Load);
|
||||
|
||||
Add(0x85, "STA", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Store);
|
||||
Add(0x95, "STA", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Store);
|
||||
Add(0x8D, "STA", AddressingMode.Absolute, 3, 4, false, InstructionType.Store);
|
||||
Add(0x9D, "STA", AddressingMode.AbsoluteX, 3, 5, false, InstructionType.Store);
|
||||
Add(0x99, "STA", AddressingMode.AbsoluteY, 3, 5, false, InstructionType.Store);
|
||||
Add(0x81, "STA", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Store);
|
||||
Add(0x91, "STA", AddressingMode.IndirectIndexed, 2, 6, false, InstructionType.Store);
|
||||
|
||||
Add(0x86, "STX", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Store);
|
||||
Add(0x96, "STX", AddressingMode.ZeroPageY, 2, 4, false, InstructionType.Store);
|
||||
Add(0x8E, "STX", AddressingMode.Absolute, 3, 4, false, InstructionType.Store);
|
||||
|
||||
Add(0x84, "STY", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Store);
|
||||
Add(0x94, "STY", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Store);
|
||||
Add(0x8C, "STY", AddressingMode.Absolute, 3, 4, false, InstructionType.Store);
|
||||
|
||||
// Register Transfers
|
||||
Add(0xAA, "TAX", AddressingMode.Implied, 1, 2, false, InstructionType.Transfer);
|
||||
Add(0x8A, "TXA", AddressingMode.Implied, 1, 2, false, InstructionType.Transfer);
|
||||
Add(0xA8, "TAY", AddressingMode.Implied, 1, 2, false, InstructionType.Transfer);
|
||||
Add(0x98, "TYA", AddressingMode.Implied, 1, 2, false, InstructionType.Transfer);
|
||||
Add(0xBA, "TSX", AddressingMode.Implied, 1, 2, false, InstructionType.Transfer);
|
||||
Add(0x9A, "TXS", AddressingMode.Implied, 1, 2, false, InstructionType.Transfer);
|
||||
|
||||
// Stack Operations
|
||||
Add(0x48, "PHA", AddressingMode.Implied, 1, 3, false, InstructionType.Stack);
|
||||
Add(0x68, "PLA", AddressingMode.Implied, 1, 4, false, InstructionType.Stack);
|
||||
Add(0x08, "PHP", AddressingMode.Implied, 1, 3, false, InstructionType.Stack);
|
||||
Add(0x28, "PLP", AddressingMode.Implied, 1, 4, false, InstructionType.Stack);
|
||||
|
||||
// Logical Operations
|
||||
Add(0x29, "AND", AddressingMode.Immediate, 2, 2, false, InstructionType.Logic);
|
||||
Add(0x25, "AND", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Logic);
|
||||
Add(0x35, "AND", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Logic);
|
||||
Add(0x2D, "AND", AddressingMode.Absolute, 3, 4, false, InstructionType.Logic);
|
||||
Add(0x3D, "AND", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Logic);
|
||||
Add(0x39, "AND", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Logic);
|
||||
Add(0x21, "AND", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Logic);
|
||||
Add(0x31, "AND", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Logic);
|
||||
|
||||
Add(0x49, "EOR", AddressingMode.Immediate, 2, 2, false, InstructionType.Logic);
|
||||
Add(0x45, "EOR", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Logic);
|
||||
Add(0x55, "EOR", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Logic);
|
||||
Add(0x4D, "EOR", AddressingMode.Absolute, 3, 4, false, InstructionType.Logic);
|
||||
Add(0x5D, "EOR", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Logic);
|
||||
Add(0x59, "EOR", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Logic);
|
||||
Add(0x41, "EOR", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Logic);
|
||||
Add(0x51, "EOR", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Logic);
|
||||
|
||||
Add(0x09, "ORA", AddressingMode.Immediate, 2, 2, false, InstructionType.Logic);
|
||||
Add(0x05, "ORA", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Logic);
|
||||
Add(0x15, "ORA", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Logic);
|
||||
Add(0x0D, "ORA", AddressingMode.Absolute, 3, 4, false, InstructionType.Logic);
|
||||
Add(0x1D, "ORA", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Logic);
|
||||
Add(0x19, "ORA", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Logic);
|
||||
Add(0x01, "ORA", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Logic);
|
||||
Add(0x11, "ORA", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Logic);
|
||||
|
||||
Add(0x24, "BIT", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Logic);
|
||||
Add(0x2C, "BIT", AddressingMode.Absolute, 3, 4, false, InstructionType.Logic);
|
||||
|
||||
// Arithmetic Operations
|
||||
Add(0x69, "ADC", AddressingMode.Immediate, 2, 2, false, InstructionType.Arithmetic);
|
||||
Add(0x65, "ADC", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Arithmetic);
|
||||
Add(0x75, "ADC", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Arithmetic);
|
||||
Add(0x6D, "ADC", AddressingMode.Absolute, 3, 4, false, InstructionType.Arithmetic);
|
||||
Add(0x7D, "ADC", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Arithmetic);
|
||||
Add(0x79, "ADC", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Arithmetic);
|
||||
Add(0x61, "ADC", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Arithmetic);
|
||||
Add(0x71, "ADC", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Arithmetic);
|
||||
|
||||
Add(0xE9, "SBC", AddressingMode.Immediate, 2, 2, false, InstructionType.Arithmetic);
|
||||
Add(0xE5, "SBC", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Arithmetic);
|
||||
Add(0xF5, "SBC", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Arithmetic);
|
||||
Add(0xED, "SBC", AddressingMode.Absolute, 3, 4, false, InstructionType.Arithmetic);
|
||||
Add(0xFD, "SBC", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Arithmetic);
|
||||
Add(0xF9, "SBC", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Arithmetic);
|
||||
Add(0xE1, "SBC", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Arithmetic);
|
||||
Add(0xF1, "SBC", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Arithmetic);
|
||||
|
||||
Add(0xC9, "CMP", AddressingMode.Immediate, 2, 2, false, InstructionType.Compare);
|
||||
Add(0xC5, "CMP", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Compare);
|
||||
Add(0xD5, "CMP", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Compare);
|
||||
Add(0xCD, "CMP", AddressingMode.Absolute, 3, 4, false, InstructionType.Compare);
|
||||
Add(0xDD, "CMP", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Compare);
|
||||
Add(0xD9, "CMP", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Compare);
|
||||
Add(0xC1, "CMP", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Compare);
|
||||
Add(0xD1, "CMP", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Compare);
|
||||
|
||||
Add(0xE0, "CPX", AddressingMode.Immediate, 2, 2, false, InstructionType.Compare);
|
||||
Add(0xE4, "CPX", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Compare);
|
||||
Add(0xEC, "CPX", AddressingMode.Absolute, 3, 4, false, InstructionType.Compare);
|
||||
|
||||
Add(0xC0, "CPY", AddressingMode.Immediate, 2, 2, false, InstructionType.Compare);
|
||||
Add(0xC4, "CPY", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Compare);
|
||||
Add(0xCC, "CPY", AddressingMode.Absolute, 3, 4, false, InstructionType.Compare);
|
||||
|
||||
// Increments & Decrements
|
||||
Add(0xE6, "INC", AddressingMode.ZeroPage, 2, 5, false, InstructionType.Increment);
|
||||
Add(0xF6, "INC", AddressingMode.ZeroPageX, 2, 6, false, InstructionType.Increment);
|
||||
Add(0xEE, "INC", AddressingMode.Absolute, 3, 6, false, InstructionType.Increment);
|
||||
Add(0xFE, "INC", AddressingMode.AbsoluteX, 3, 7, false, InstructionType.Increment);
|
||||
|
||||
Add(0xE8, "INX", AddressingMode.Implied, 1, 2, false, InstructionType.Increment);
|
||||
Add(0xC8, "INY", AddressingMode.Implied, 1, 2, false, InstructionType.Increment);
|
||||
|
||||
Add(0xC6, "DEC", AddressingMode.ZeroPage, 2, 5, false, InstructionType.Decrement);
|
||||
Add(0xD6, "DEC", AddressingMode.ZeroPageX, 2, 6, false, InstructionType.Decrement);
|
||||
Add(0xCE, "DEC", AddressingMode.Absolute, 3, 6, false, InstructionType.Decrement);
|
||||
Add(0xDE, "DEC", AddressingMode.AbsoluteX, 3, 7, false, InstructionType.Decrement);
|
||||
|
||||
Add(0xCA, "DEX", AddressingMode.Implied, 1, 2, false, InstructionType.Decrement);
|
||||
Add(0x88, "DEY", AddressingMode.Implied, 1, 2, false, InstructionType.Decrement);
|
||||
|
||||
// Shifts
|
||||
Add(0x0A, "ASL", AddressingMode.Accumulator, 1, 2, false, InstructionType.Shift);
|
||||
Add(0x06, "ASL", AddressingMode.ZeroPage, 2, 5, false, InstructionType.Shift);
|
||||
Add(0x16, "ASL", AddressingMode.ZeroPageX, 2, 6, false, InstructionType.Shift);
|
||||
Add(0x0E, "ASL", AddressingMode.Absolute, 3, 6, false, InstructionType.Shift);
|
||||
Add(0x1E, "ASL", AddressingMode.AbsoluteX, 3, 7, false, InstructionType.Shift);
|
||||
|
||||
Add(0x4A, "LSR", AddressingMode.Accumulator, 1, 2, false, InstructionType.Shift);
|
||||
Add(0x46, "LSR", AddressingMode.ZeroPage, 2, 5, false, InstructionType.Shift);
|
||||
Add(0x56, "LSR", AddressingMode.ZeroPageX, 2, 6, false, InstructionType.Shift);
|
||||
Add(0x4E, "LSR", AddressingMode.Absolute, 3, 6, false, InstructionType.Shift);
|
||||
Add(0x5E, "LSR", AddressingMode.AbsoluteX, 3, 7, false, InstructionType.Shift);
|
||||
|
||||
Add(0x2A, "ROL", AddressingMode.Accumulator, 1, 2, false, InstructionType.Shift);
|
||||
Add(0x26, "ROL", AddressingMode.ZeroPage, 2, 5, false, InstructionType.Shift);
|
||||
Add(0x36, "ROL", AddressingMode.ZeroPageX, 2, 6, false, InstructionType.Shift);
|
||||
Add(0x2E, "ROL", AddressingMode.Absolute, 3, 6, false, InstructionType.Shift);
|
||||
Add(0x3E, "ROL", AddressingMode.AbsoluteX, 3, 7, false, InstructionType.Shift);
|
||||
|
||||
Add(0x6A, "ROR", AddressingMode.Accumulator, 1, 2, false, InstructionType.Shift);
|
||||
Add(0x66, "ROR", AddressingMode.ZeroPage, 2, 5, false, InstructionType.Shift);
|
||||
Add(0x76, "ROR", AddressingMode.ZeroPageX, 2, 6, false, InstructionType.Shift);
|
||||
Add(0x6E, "ROR", AddressingMode.Absolute, 3, 6, false, InstructionType.Shift);
|
||||
Add(0x7E, "ROR", AddressingMode.AbsoluteX, 3, 7, false, InstructionType.Shift);
|
||||
|
||||
// Jumps & Calls
|
||||
Add(0x4C, "JMP", AddressingMode.Absolute, 3, 3, false, InstructionType.Jump);
|
||||
Add(0x6C, "JMP", AddressingMode.Indirect, 3, 5, false, InstructionType.Jump);
|
||||
Add(0x20, "JSR", AddressingMode.Absolute, 3, 6, false, InstructionType.Jump);
|
||||
Add(0x60, "RTS", AddressingMode.Implied, 1, 6, false, InstructionType.Return);
|
||||
Add(0x40, "RTI", AddressingMode.Implied, 1, 6, false, InstructionType.Return);
|
||||
|
||||
// Branches
|
||||
Add(0x90, "BCC", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
|
||||
Add(0xB0, "BCS", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
|
||||
Add(0xF0, "BEQ", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
|
||||
Add(0x30, "BMI", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
|
||||
Add(0xD0, "BNE", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
|
||||
Add(0x10, "BPL", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
|
||||
Add(0x50, "BVC", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
|
||||
Add(0x70, "BVS", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
|
||||
|
||||
// Status Flag Changes
|
||||
Add(0x18, "CLC", AddressingMode.Implied, 1, 2, false, InstructionType.Clear);
|
||||
Add(0xD8, "CLD", AddressingMode.Implied, 1, 2, false, InstructionType.Clear);
|
||||
Add(0x58, "CLI", AddressingMode.Implied, 1, 2, false, InstructionType.Clear);
|
||||
Add(0xB8, "CLV", AddressingMode.Implied, 1, 2, false, InstructionType.Clear);
|
||||
Add(0x38, "SEC", AddressingMode.Implied, 1, 2, false, InstructionType.Set);
|
||||
Add(0xF8, "SED", AddressingMode.Implied, 1, 2, false, InstructionType.Set);
|
||||
Add(0x78, "SEI", AddressingMode.Implied, 1, 2, false, InstructionType.Set);
|
||||
|
||||
// System Functions
|
||||
Add(0x00, "BRK", AddressingMode.Implied, 1, 7, false, InstructionType.Interrupt);
|
||||
Add(0xEA, "NOP", AddressingMode.Implied, 1, 2, false, InstructionType.Other);
|
||||
|
||||
// Unofficial/Illegal opcodes are added here if needed by further forks, however this should cover all of the NES 6502 instructions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an instruction to the instruction set
|
||||
/// </summary>
|
||||
private static void Add(byte opcode, string mnemonic, AddressingMode addressingMode,
|
||||
byte size, byte cycles, bool extraCycleOnPageCross, InstructionType type)
|
||||
{
|
||||
var instruction = new InstructionInfo(
|
||||
opcode, mnemonic, addressingMode, size, cycles, extraCycleOnPageCross, type);
|
||||
|
||||
instructions[opcode] = instruction;
|
||||
mnemonicMap[(mnemonic, addressingMode)] = instruction;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,459 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NESDecompiler.Core.CPU;
|
||||
using NESDecompiler.Core.Exceptions;
|
||||
using NESDecompiler.Core.ROM;
|
||||
|
||||
namespace NESDecompiler.Core.Disassembly
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a disassembled instruction with its address and operands
|
||||
/// </summary>
|
||||
public class DisassembledInstruction
|
||||
{
|
||||
/// <summary>
|
||||
/// The address of this instruction in the ROM
|
||||
/// </summary>
|
||||
public ushort Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The CPU memory address this instruction maps to
|
||||
/// </summary>
|
||||
public ushort CPUAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about this instruction's opcode
|
||||
/// </summary>
|
||||
public InstructionInfo Info { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The raw bytes of this instruction (including operands)
|
||||
/// </summary>
|
||||
public byte[] Bytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The operand bytes of this instruction
|
||||
/// </summary>
|
||||
public byte[] Operands => Bytes.Length > 1 ? Bytes[1..] : Array.Empty<byte>();
|
||||
|
||||
/// <summary>
|
||||
/// The target address for branch and jump instructions
|
||||
/// </summary>
|
||||
public ushort? TargetAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Potential label for this instruction
|
||||
/// </summary>
|
||||
public string? Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Potential comment for this instruction
|
||||
/// </summary>
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this instruction is a potential function entry point
|
||||
/// </summary>
|
||||
public bool IsFunctionEntry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this instruction is a potential function exit point
|
||||
/// </summary>
|
||||
public bool IsFunctionExit => Info.Mnemonic == "RTS" || Info.Mnemonic == "RTI";
|
||||
|
||||
/// <summary>
|
||||
/// Whether this instruction is a branch instruction
|
||||
/// </summary>
|
||||
public bool IsBranch => Info.Type == InstructionType.Branch;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this instruction is a jump instruction
|
||||
/// </summary>
|
||||
public bool IsJump => Info.Mnemonic == "JMP" || Info.Mnemonic == "JSR";
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of this instruction
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (!string.IsNullOrEmpty(Label))
|
||||
{
|
||||
sb.AppendLine($"{Label}:");
|
||||
}
|
||||
|
||||
sb.Append($"{CPUAddress:X4} ");
|
||||
foreach (var b in Bytes)
|
||||
{
|
||||
sb.Append($"{b:X2} ");
|
||||
}
|
||||
|
||||
sb.Append(new string(' ', (3 - Bytes.Length) * 3 + 2));
|
||||
|
||||
sb.Append(Info.Mnemonic);
|
||||
|
||||
if (Info.AddressingMode != AddressingMode.Implied &&
|
||||
Info.AddressingMode != AddressingMode.Accumulator)
|
||||
{
|
||||
sb.Append(' ');
|
||||
|
||||
if (Info.AddressingMode == AddressingMode.Relative && TargetAddress.HasValue)
|
||||
{
|
||||
sb.Append($"${TargetAddress.Value:X4}");
|
||||
}
|
||||
else if (Operands.Length == 1)
|
||||
{
|
||||
string operandFormat = Info.GetOperandFormat();
|
||||
sb.Append(string.Format(operandFormat, Operands[0]));
|
||||
}
|
||||
else if (Operands.Length == 2)
|
||||
{
|
||||
string operandFormat = Info.GetOperandFormat();
|
||||
ushort value = (ushort)((Operands[1] << 8) | Operands[0]);
|
||||
sb.Append(string.Format(operandFormat, value));
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Comment))
|
||||
{
|
||||
sb.Append($" ; {Comment}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disassembles 6502 machine code into assembly language
|
||||
/// </summary>
|
||||
public class Disassembler
|
||||
{
|
||||
private ROMInfo romInfo;
|
||||
private byte[] codeData;
|
||||
private List<DisassembledInstruction> instructions;
|
||||
private Dictionary<ushort, DisassembledInstruction> addressToInstruction;
|
||||
private HashSet<ushort> entryPoints;
|
||||
private HashSet<ushort> referencedAddresses;
|
||||
private Dictionary<ushort, string> labels;
|
||||
private int labelCounter;
|
||||
|
||||
/// <summary>
|
||||
/// The list of disassembled instructions
|
||||
/// </summary>
|
||||
public IReadOnlyList<DisassembledInstruction> Instructions => instructions;
|
||||
|
||||
/// <summary>
|
||||
/// Maps CPU addresses to disassembled instructions
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<ushort, DisassembledInstruction> AddressToInstruction => addressToInstruction;
|
||||
|
||||
/// <summary>
|
||||
/// The list of entry points (e.g., reset vector, NMI vector)
|
||||
/// </summary>
|
||||
public IReadOnlySet<ushort> EntryPoints => entryPoints;
|
||||
|
||||
/// <summary>
|
||||
/// The list of addresses referenced by the code
|
||||
/// </summary>
|
||||
public IReadOnlySet<ushort> ReferencedAddresses => referencedAddresses;
|
||||
|
||||
/// <summary>
|
||||
/// Maps CPU addresses to labels
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<ushort, string> Labels => labels;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new disassembler for the specified ROM
|
||||
/// </summary>
|
||||
/// <param name="romInfo">Information about the ROM</param>
|
||||
/// <param name="codeData">The code data to disassemble</param>
|
||||
public Disassembler(ROMInfo romInfo, byte[] codeData)
|
||||
{
|
||||
this.romInfo = romInfo ?? throw new ArgumentNullException(nameof(romInfo));
|
||||
this.codeData = codeData ?? throw new ArgumentNullException(nameof(codeData));
|
||||
|
||||
instructions = new List<DisassembledInstruction>();
|
||||
addressToInstruction = new Dictionary<ushort, DisassembledInstruction>();
|
||||
entryPoints = new HashSet<ushort>();
|
||||
referencedAddresses = new HashSet<ushort>();
|
||||
labels = new Dictionary<ushort, string>();
|
||||
labelCounter = 0;
|
||||
|
||||
if (romInfo.ResetVector != 0)
|
||||
{
|
||||
entryPoints.Add(romInfo.ResetVector);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disassembles the code data
|
||||
/// </summary>
|
||||
public void Disassemble()
|
||||
{
|
||||
LinearDisassembly();
|
||||
TraceExecution();
|
||||
IdentifyFunctions();
|
||||
GenerateLabels();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the disassembled instruction at the specified address
|
||||
/// </summary>
|
||||
/// <param name="address">The CPU address</param>
|
||||
/// <returns>The disassembled instruction, or null if not found</returns>
|
||||
public DisassembledInstruction? GetInstructionAt(ushort address)
|
||||
{
|
||||
addressToInstruction.TryGetValue(address, out var instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a linear disassembly of the code data
|
||||
/// </summary>
|
||||
private void LinearDisassembly()
|
||||
{
|
||||
try
|
||||
{
|
||||
int offset = 0;
|
||||
|
||||
ushort baseAddress = 0x8000;
|
||||
|
||||
while (offset < codeData.Length)
|
||||
{
|
||||
ushort cpuAddress = (ushort)(baseAddress + offset);
|
||||
|
||||
byte opcode = codeData[offset];
|
||||
|
||||
var instructionInfo = InstructionSet.GetInstruction(opcode);
|
||||
|
||||
if (!instructionInfo.IsValid)
|
||||
{
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (offset + instructionInfo.Size > codeData.Length)
|
||||
{
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[instructionInfo.Size];
|
||||
Array.Copy(codeData, offset, bytes, 0, instructionInfo.Size);
|
||||
|
||||
var instruction = new DisassembledInstruction
|
||||
{
|
||||
Address = (ushort)offset,
|
||||
CPUAddress = cpuAddress,
|
||||
Info = instructionInfo,
|
||||
Bytes = bytes
|
||||
};
|
||||
|
||||
CalculateTargetAddress(instruction);
|
||||
|
||||
instructions.Add(instruction);
|
||||
addressToInstruction[cpuAddress] = instruction;
|
||||
offset += instructionInfo.Size;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DisassemblyException($"Error during linear disassembly: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traces execution from known entry points
|
||||
/// </summary>
|
||||
private void TraceExecution()
|
||||
{
|
||||
try
|
||||
{
|
||||
var toTrace = new Queue<ushort>(entryPoints);
|
||||
var traced = new HashSet<ushort>();
|
||||
|
||||
while (toTrace.Count > 0)
|
||||
{
|
||||
ushort address = toTrace.Dequeue();
|
||||
|
||||
if (traced.Contains(address))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
traced.Add(address);
|
||||
|
||||
if (!addressToInstruction.TryGetValue(address, out var instruction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entryPoints.Contains(address))
|
||||
{
|
||||
instruction.IsFunctionEntry = true;
|
||||
}
|
||||
|
||||
if (instruction.IsJump)
|
||||
{
|
||||
if (instruction.TargetAddress.HasValue)
|
||||
{
|
||||
ushort target = instruction.TargetAddress.Value;
|
||||
|
||||
referencedAddresses.Add(target);
|
||||
|
||||
if (instruction.Info.Mnemonic == "JSR")
|
||||
{
|
||||
entryPoints.Add(target);
|
||||
|
||||
ushort returnAddress = (ushort)(address + instruction.Info.Size);
|
||||
toTrace.Enqueue(returnAddress);
|
||||
}
|
||||
|
||||
toTrace.Enqueue(target);
|
||||
|
||||
if (instruction.Info.Mnemonic == "JMP")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (instruction.IsBranch)
|
||||
{
|
||||
if (instruction.TargetAddress.HasValue)
|
||||
{
|
||||
ushort target = instruction.TargetAddress.Value;
|
||||
|
||||
referencedAddresses.Add(target);
|
||||
|
||||
toTrace.Enqueue(target);
|
||||
}
|
||||
}
|
||||
else if (instruction.IsFunctionExit)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ushort nextAddress = (ushort)(address + instruction.Info.Size);
|
||||
toTrace.Enqueue(nextAddress);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DisassemblyException($"Error during execution tracing: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies functions and their boundaries
|
||||
/// </summary>
|
||||
private void IdentifyFunctions()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (ushort entryPoint in entryPoints)
|
||||
{
|
||||
if (addressToInstruction.TryGetValue(entryPoint, out var instruction))
|
||||
{
|
||||
instruction.IsFunctionEntry = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DisassemblyException($"Error during function identification: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates labels for referenced addresses
|
||||
/// </summary>
|
||||
private void GenerateLabels()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (ushort entryPoint in entryPoints)
|
||||
{
|
||||
if (addressToInstruction.TryGetValue(entryPoint, out var instruction))
|
||||
{
|
||||
string label = $"sub_{entryPoint:X4}";
|
||||
instruction.Label = label;
|
||||
labels[entryPoint] = label;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ushort address in referencedAddresses)
|
||||
{
|
||||
if (!labels.ContainsKey(address) && addressToInstruction.TryGetValue(address, out var instruction))
|
||||
{
|
||||
string label = $"loc_{labelCounter++:X4}";
|
||||
instruction.Label = label;
|
||||
labels[address] = label;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
if (instruction.TargetAddress.HasValue)
|
||||
{
|
||||
ushort target = instruction.TargetAddress.Value;
|
||||
if (labels.TryGetValue(target, out string label))
|
||||
{
|
||||
instruction.Comment = $"-> {label}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DisassemblyException($"Error during label generation: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the target address for branch and jump instructions
|
||||
/// </summary>
|
||||
/// <param name="instruction">The instruction to process</param>
|
||||
private void CalculateTargetAddress(DisassembledInstruction instruction)
|
||||
{
|
||||
if (instruction.Info.AddressingMode == AddressingMode.Relative)
|
||||
{
|
||||
// Branch instructions use relative addressing
|
||||
// The offset is signed and relative to the next instruction
|
||||
sbyte offset = (sbyte)instruction.Operands[0];
|
||||
ushort nextAddress = (ushort)(instruction.CPUAddress + instruction.Info.Size);
|
||||
instruction.TargetAddress = (ushort)(nextAddress + offset);
|
||||
}
|
||||
else if (instruction.IsJump &&
|
||||
(instruction.Info.AddressingMode == AddressingMode.Absolute ||
|
||||
instruction.Info.AddressingMode == AddressingMode.Indirect))
|
||||
{
|
||||
if (instruction.Operands.Length == 2)
|
||||
{
|
||||
ushort target = (ushort)((instruction.Operands[1] << 8) | instruction.Operands[0]);
|
||||
instruction.TargetAddress = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the disassembly as a formatted string
|
||||
/// </summary>
|
||||
public string ToAssemblyString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("; 6502 Disassembly");
|
||||
sb.AppendLine($"; ROM: {romInfo}");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
sb.AppendLine(instruction.ToString());
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
|
||||
namespace NESDecompiler.Core.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Base exception class for ROM-related errors
|
||||
/// </summary>
|
||||
public class ROMException : Exception
|
||||
{
|
||||
public ROMException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ROMException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when a ROM has an invalid format
|
||||
/// </summary>
|
||||
public class InvalidROMFormatException : ROMException
|
||||
{
|
||||
public InvalidROMFormatException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidROMFormatException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown during the disassembly process
|
||||
/// </summary>
|
||||
public class DisassemblyException : Exception
|
||||
{
|
||||
public DisassemblyException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public DisassemblyException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown during the decompilation process
|
||||
/// </summary>
|
||||
public class DecompilationException : Exception
|
||||
{
|
||||
public DecompilationException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public DecompilationException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NESDecompiler.Core.ROM
|
||||
{
|
||||
/// <summary>
|
||||
/// Mirroring types for NES hardware
|
||||
/// </summary>
|
||||
public enum MirroringType
|
||||
{
|
||||
Horizontal,
|
||||
Vertical,
|
||||
FourScreen
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about a loaded NES ROM
|
||||
/// </summary>
|
||||
public class ROMInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Size of the PRG ROM in bytes
|
||||
/// </summary>
|
||||
public int PRGROMSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the CHR ROM in bytes
|
||||
/// </summary>
|
||||
public int CHRROMSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Mapper number
|
||||
/// </summary>
|
||||
public byte MapperNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of mirroring used
|
||||
/// </summary>
|
||||
public MirroringType MirroringType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the ROM has battery-backed RAM
|
||||
/// </summary>
|
||||
public bool HasBatteryBackedRAM { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the ROM has a trainer
|
||||
/// </summary>
|
||||
public bool HasTrainer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the ROM uses four-screen VRAM
|
||||
/// </summary>
|
||||
public bool HasFourScreenVRAM { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the ROM is a VS System cartridge
|
||||
/// </summary>
|
||||
public bool IsVSSystemCart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset of the PRG ROM in the file
|
||||
/// </summary>
|
||||
public int PRGROMOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset of the CHR ROM in the file
|
||||
/// </summary>
|
||||
public int CHRROMOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Reset Vector (entry point) address
|
||||
/// </summary>
|
||||
public ushort ResetVector { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The raw ROM data for reference
|
||||
/// </summary>
|
||||
public byte[] RawData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of identified entry points (including reset vector and NMI)
|
||||
/// </summary>
|
||||
public HashSet<ushort> EntryPoints { get; } = new HashSet<ushort>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the ROM information
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"ROM Info:\n" +
|
||||
$" PRG ROM: {PRGROMSize} bytes\n" +
|
||||
$" CHR ROM: {CHRROMSize} bytes\n" +
|
||||
$" Mapper: {MapperNumber}\n" +
|
||||
$" Mirroring: {MirroringType}\n" +
|
||||
$" Battery-Backed RAM: {HasBatteryBackedRAM}\n" +
|
||||
$" Reset Vector: ${ResetVector:X4}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using NESDecompiler.Core.Exceptions;
|
||||
|
||||
namespace NESDecompiler.Core.ROM
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles loading and parsing NES ROM files
|
||||
/// </summary>
|
||||
public class ROMLoader
|
||||
{
|
||||
// iNES header constants
|
||||
private const int HEADER_SIZE = 16;
|
||||
private const int PRG_ROM_SIZE_OFFSET = 4;
|
||||
private const int CHR_ROM_SIZE_OFFSET = 5;
|
||||
private const int FLAGS_6_OFFSET = 6;
|
||||
private const int FLAGS_7_OFFSET = 7;
|
||||
private const int PRG_RAM_SIZE_OFFSET = 8;
|
||||
private const int FLAGS_9_OFFSET = 9;
|
||||
private const int FLAGS_10_OFFSET = 10;
|
||||
|
||||
// ROM data
|
||||
private byte[] romData;
|
||||
private ROMInfo romInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Information about the loaded ROM
|
||||
/// </summary>
|
||||
public ROMInfo ROMInfo => romInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Loads a NES ROM file from disk
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the ROM file</param>
|
||||
/// <returns>Information about the loaded ROM</returns>
|
||||
public ROMInfo LoadFromFile(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException("ROM file not found", filePath);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
romData = File.ReadAllBytes(filePath);
|
||||
return ParseROMHeader();
|
||||
}
|
||||
catch (Exception ex) when (ex is not ROMException)
|
||||
{
|
||||
throw new ROMException($"Failed to load ROM file: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a NES ROM from a byte array
|
||||
/// </summary>
|
||||
/// <param name="data">The ROM data</param>
|
||||
/// <returns>Information about the loaded ROM</returns>
|
||||
public ROMInfo LoadFromBytes(byte[] data)
|
||||
{
|
||||
if (data == null || data.Length < HEADER_SIZE)
|
||||
{
|
||||
throw new ROMException("Invalid ROM data: too short");
|
||||
}
|
||||
|
||||
romData = data;
|
||||
return ParseROMHeader();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the iNES ROM header
|
||||
/// </summary>
|
||||
/// <returns>Information about the ROM</returns>
|
||||
private ROMInfo ParseROMHeader()
|
||||
{
|
||||
// Verify iNES header signature "NES" followed by MS-DOS EOF
|
||||
if (romData.Length < HEADER_SIZE ||
|
||||
romData[0] != 0x4E || romData[1] != 0x45 || romData[2] != 0x53 || romData[3] != 0x1A)
|
||||
{
|
||||
throw new InvalidROMFormatException("Invalid iNES ROM header");
|
||||
}
|
||||
|
||||
romInfo = new ROMInfo
|
||||
{
|
||||
PRGROMSize = romData[PRG_ROM_SIZE_OFFSET] * 16384, // 16KB units
|
||||
CHRROMSize = romData[CHR_ROM_SIZE_OFFSET] * 8192, // 8KB units
|
||||
|
||||
MapperNumber = (byte)((romData[FLAGS_7_OFFSET] & 0xF0) | ((romData[FLAGS_6_OFFSET] & 0xF0) >> 4)),
|
||||
|
||||
MirroringType = (romData[FLAGS_6_OFFSET] & 0x01) == 0
|
||||
? MirroringType.Horizontal
|
||||
: MirroringType.Vertical,
|
||||
|
||||
HasBatteryBackedRAM = (romData[FLAGS_6_OFFSET] & 0x02) != 0,
|
||||
|
||||
HasTrainer = (romData[FLAGS_6_OFFSET] & 0x04) != 0,
|
||||
|
||||
HasFourScreenVRAM = (romData[FLAGS_6_OFFSET] & 0x08) != 0,
|
||||
|
||||
IsVSSystemCart = (romData[FLAGS_7_OFFSET] & 0x01) != 0,
|
||||
|
||||
PRGROMOffset = HEADER_SIZE + (((romData[FLAGS_6_OFFSET] & 0x04) != 0) ? 512 : 0),
|
||||
|
||||
RawData = romData
|
||||
};
|
||||
|
||||
romInfo.CHRROMOffset = romInfo.PRGROMOffset + romInfo.PRGROMSize;
|
||||
|
||||
// Identify entry points (reset vector)
|
||||
if (romInfo.PRGROMSize > 0)
|
||||
{
|
||||
// In 6502, reset vector is at 0xFFFC-0xFFFD
|
||||
// For NES, this is mapped to the end of the first PRG ROM bank
|
||||
int resetVectorOffset = romInfo.PRGROMOffset + romInfo.PRGROMSize - 4;
|
||||
if (resetVectorOffset >= 0 && resetVectorOffset < romData.Length - 1)
|
||||
{
|
||||
romInfo.ResetVector = (ushort)(romData[resetVectorOffset] | (romData[resetVectorOffset + 1] << 8));
|
||||
}
|
||||
}
|
||||
|
||||
return romInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a segment of the ROM data
|
||||
/// </summary>
|
||||
/// <param name="offset">Starting offset</param>
|
||||
/// <param name="length">Number of bytes to read</param>
|
||||
/// <returns>The requested data segment</returns>
|
||||
public byte[] GetROMSegment(int offset, int length)
|
||||
{
|
||||
if (romData == null)
|
||||
throw new ROMException("No ROM data loaded");
|
||||
|
||||
if (offset < 0 || offset + length > romData.Length)
|
||||
throw new ROMException("Requested segment is out of bounds");
|
||||
|
||||
byte[] segment = new byte[length];
|
||||
Array.Copy(romData, offset, segment, 0, length);
|
||||
return segment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PRG ROM data
|
||||
/// </summary>
|
||||
/// <returns>The PRG ROM data</returns>
|
||||
public byte[] GetPRGROMData()
|
||||
{
|
||||
if (romInfo == null)
|
||||
throw new ROMException("No ROM data loaded");
|
||||
|
||||
return GetROMSegment(romInfo.PRGROMOffset, romInfo.PRGROMSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CHR ROM data
|
||||
/// </summary>
|
||||
/// <returns>The CHR ROM data</returns>
|
||||
public byte[] GetCHRROMData()
|
||||
{
|
||||
if (romInfo == null)
|
||||
throw new ROMException("No ROM data loaded");
|
||||
|
||||
if (romInfo.CHRROMSize == 0)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
return GetROMSegment(romInfo.CHRROMOffset, romInfo.CHRROMSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Application x:Class="NESDecompiler.GUI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:NESDecompiler.GUI"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Resources/Themes/ThemeManager.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Windows;
|
||||
|
||||
namespace NESDecompiler.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
using ICSharpCode.AvalonEdit;
|
||||
|
||||
namespace NESDecompiler.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Behavior for binding the text of an AvalonEdit control
|
||||
/// </summary>
|
||||
public class AvalonEditBehavior : Behavior<TextEditor>
|
||||
{
|
||||
public static readonly DependencyProperty TextProperty =
|
||||
DependencyProperty.Register("Text", typeof(string), typeof(AvalonEditBehavior),
|
||||
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PropertyChangedCallback));
|
||||
|
||||
/// <summary>
|
||||
/// The bound text property
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get { return (string)GetValue(TextProperty); }
|
||||
set { SetValue(TextProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the Text property changes
|
||||
/// </summary>
|
||||
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var behavior = d as AvalonEditBehavior;
|
||||
if (behavior != null && behavior.AssociatedObject != null)
|
||||
{
|
||||
var editor = behavior.AssociatedObject;
|
||||
|
||||
if (editor.Text != e.NewValue as string)
|
||||
{
|
||||
editor.Text = e.NewValue as string ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the behavior is attached to the control
|
||||
/// </summary>
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
|
||||
if (AssociatedObject != null)
|
||||
{
|
||||
AssociatedObject.Text = Text ?? string.Empty;
|
||||
|
||||
AssociatedObject.TextChanged += OnTextChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the behavior is detached from the control
|
||||
/// </summary>
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
if (AssociatedObject != null)
|
||||
{
|
||||
AssociatedObject.TextChanged -= OnTextChanged;
|
||||
}
|
||||
|
||||
base.OnDetaching();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the text in the editor changes
|
||||
/// </summary>
|
||||
private void OnTextChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (AssociatedObject != null)
|
||||
{
|
||||
Text = AssociatedObject.Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using ICSharpCode.AvalonEdit;
|
||||
|
||||
namespace NESDecompiler.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides attached properties for binding text to AvalonEdit
|
||||
/// </summary>
|
||||
public static class AvalonEditTextBindingBehavior
|
||||
{
|
||||
public static readonly DependencyProperty TextProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"Text",
|
||||
typeof(string),
|
||||
typeof(AvalonEditTextBindingBehavior),
|
||||
new FrameworkPropertyMetadata(
|
||||
default(string),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
OnTextChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text value
|
||||
/// </summary>
|
||||
public static string GetText(DependencyObject obj)
|
||||
{
|
||||
return (string)obj.GetValue(TextProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the text value
|
||||
/// </summary>
|
||||
public static void SetText(DependencyObject obj, string value)
|
||||
{
|
||||
obj.SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the text property changes
|
||||
/// </summary>
|
||||
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is TextEditor editor)
|
||||
{
|
||||
if (editor.Text != (string)e.NewValue)
|
||||
{
|
||||
editor.Text = (string)e.NewValue ?? string.Empty;
|
||||
}
|
||||
|
||||
if (e.OldValue != null)
|
||||
{
|
||||
editor.TextChanged -= Editor_TextChanged;
|
||||
}
|
||||
|
||||
if (e.NewValue != null)
|
||||
{
|
||||
editor.TextChanged += Editor_TextChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the editor text changes
|
||||
/// </summary>
|
||||
private static void Editor_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is TextEditor editor)
|
||||
{
|
||||
SetText(editor, editor.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace NESDecompiler.GUI.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// A command whose sole purpose is to relay its functionality to other
|
||||
/// objects by invoking delegates.
|
||||
/// </summary>
|
||||
public class RelayCommand : ICommand
|
||||
{
|
||||
private readonly Action execute;
|
||||
private readonly Func<bool>? canExecute;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new command that can always execute.
|
||||
/// </summary>
|
||||
/// <param name="execute">The execution logic.</param>
|
||||
public RelayCommand(Action execute)
|
||||
: this(execute, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new command.
|
||||
/// </summary>
|
||||
/// <param name="execute">The execution logic.</param>
|
||||
/// <param name="canExecute">The execution status logic.</param>
|
||||
public RelayCommand(Action execute, Func<bool>? canExecute)
|
||||
{
|
||||
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
||||
this.canExecute = canExecute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this command can execute in its current state.
|
||||
/// </summary>
|
||||
/// <param name="parameter">
|
||||
/// Data used by the command. If the command does not require data to be passed,
|
||||
/// this object can be set to null.
|
||||
/// </param>
|
||||
/// <returns>true if this command can be executed; otherwise, false.</returns>
|
||||
public bool CanExecute(object? parameter)
|
||||
{
|
||||
return canExecute == null || canExecute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the command on the current command target.
|
||||
/// </summary>
|
||||
/// <param name="parameter">
|
||||
/// Data used by the command. If the command does not require data to be passed,
|
||||
/// this object can be set to null.
|
||||
/// </param>
|
||||
public void Execute(object? parameter)
|
||||
{
|
||||
execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when changes occur that affect whether or not the command should execute.
|
||||
/// </summary>
|
||||
public event EventHandler? CanExecuteChanged
|
||||
{
|
||||
add { CommandManager.RequerySuggested += value; }
|
||||
remove { CommandManager.RequerySuggested -= value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the CanExecuteChanged event.
|
||||
/// </summary>
|
||||
public void RaiseCanExecuteChanged()
|
||||
{
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A generic command whose sole purpose is to relay its functionality to other
|
||||
/// objects by invoking delegates.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of parameter that this command expects.</typeparam>
|
||||
public class RelayCommand<T> : ICommand
|
||||
{
|
||||
private readonly Action<T?> execute;
|
||||
private readonly Predicate<T?>? canExecute;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new command that can always execute.
|
||||
/// </summary>
|
||||
/// <param name="execute">The execution logic.</param>
|
||||
public RelayCommand(Action<T?> execute)
|
||||
: this(execute, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new command.
|
||||
/// </summary>
|
||||
/// <param name="execute">The execution logic.</param>
|
||||
/// <param name="canExecute">The execution status logic.</param>
|
||||
public RelayCommand(Action<T?> execute, Predicate<T?>? canExecute)
|
||||
{
|
||||
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
||||
this.canExecute = canExecute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this command can execute in its current state.
|
||||
/// </summary>
|
||||
/// <param name="parameter">
|
||||
/// Data used by the command. If the command does not require data to be passed,
|
||||
/// this object can be set to null.
|
||||
/// </param>
|
||||
/// <returns>true if this command can be executed; otherwise, false.</returns>
|
||||
public bool CanExecute(object? parameter)
|
||||
{
|
||||
return canExecute == null || canExecute((T?)parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the command on the current command target.
|
||||
/// </summary>
|
||||
/// <param name="parameter">
|
||||
/// Data used by the command. If the command does not require data to be passed,
|
||||
/// this object can be set to null.
|
||||
/// </param>
|
||||
public void Execute(object? parameter)
|
||||
{
|
||||
execute((T?)parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when changes occur that affect whether or not the command should execute.
|
||||
/// </summary>
|
||||
public event EventHandler? CanExecuteChanged
|
||||
{
|
||||
add { CommandManager.RequerySuggested += value; }
|
||||
remove { CommandManager.RequerySuggested -= value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the CanExecuteChanged event.
|
||||
/// </summary>
|
||||
public void RaiseCanExecuteChanged()
|
||||
{
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Window x:Class="NESDecompiler.GUI.InputDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Input" Height="150" Width="400"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ShowInTaskbar="False"
|
||||
ResizeMode="NoResize">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock x:Name="PromptText" Grid.Row="0" Text="Enter value:" Margin="0,0,0,10"/>
|
||||
<TextBox x:Name="ResponseTextBox" Grid.Row="1" Margin="0,0,0,10"/>
|
||||
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Content="OK" Width="75" Click="OKButton_Click" IsDefault="True" Margin="0,0,5,0"/>
|
||||
<Button Content="Cancel" Width="75" Click="CancelButton_Click" IsCancel="True"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace NESDecompiler.GUI
|
||||
{
|
||||
public partial class InputDialog : Window
|
||||
{
|
||||
public string ResponseText
|
||||
{
|
||||
get { return ResponseTextBox.Text; }
|
||||
set { ResponseTextBox.Text = value; }
|
||||
}
|
||||
|
||||
public InputDialog(string title, string prompt, string defaultResponse = "")
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Title = title;
|
||||
PromptText.Text = prompt;
|
||||
ResponseText = defaultResponse;
|
||||
|
||||
ResponseTextBox.SelectAll();
|
||||
ResponseTextBox.Focus();
|
||||
}
|
||||
|
||||
private void OKButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
<Window x:Class="NESDecompiler.GUI.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:NESDecompiler.GUI"
|
||||
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
|
||||
mc:Ignorable="d"
|
||||
Title="NES Decompiler" Height="800" Width="1200">
|
||||
|
||||
<Window.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVis" />
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Menu Grid.Row="0">
|
||||
<MenuItem Header="_File">
|
||||
<MenuItem Header="_Open ROM..." Command="{Binding OpenROMCommand}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="_Save Disassembly..." Command="{Binding SaveDisassemblyCommand}" IsEnabled="{Binding IsROMLoaded}"/>
|
||||
<MenuItem Header="Save _C Code..." Command="{Binding SaveCCodeCommand}" IsEnabled="{Binding IsROMLoaded}"/>
|
||||
<MenuItem Header="Save _Header..." Command="{Binding SaveHeaderCommand}" IsEnabled="{Binding IsROMLoaded}"/>
|
||||
<MenuItem Header="Save to _Workspace..." Command="{Binding SaveWorkspaceCommand}" IsEnabled="{Binding IsROMLoaded}"/>
|
||||
<MenuItem Header="E_xport to VS2022..." Command="{Binding ExportToVSCommand}" IsEnabled="{Binding IsDecompiled}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="E_xit" Command="{Binding ExitCommand}"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Project">
|
||||
<MenuItem Header="_New Project" Command="{Binding NewProjectCommand}"/>
|
||||
<MenuItem Header="_Open Project..." Command="{Binding OpenProjectCommand}"/>
|
||||
<MenuItem Header="_Save Project" Command="{Binding SaveProjectCommand}" IsEnabled="{Binding HasProject}"/>
|
||||
<MenuItem Header="Save Project _As..." Command="{Binding SaveProjectAsCommand}" IsEnabled="{Binding HasProject}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="Add _ROM to Project..." Command="{Binding AddROMCommand}" IsEnabled="{Binding HasProject}"/>
|
||||
<MenuItem Header="_Remove Active ROM" Command="{Binding RemoveROMCommand}" IsEnabled="{Binding IsROMLoaded}"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Actions">
|
||||
<MenuItem Header="_Disassemble" Command="{Binding DisassembleCommand}" IsEnabled="{Binding IsROMLoaded}"/>
|
||||
<MenuItem Header="_Decompile" Command="{Binding DecompileCommand}" IsEnabled="{Binding IsDisassembled}"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_View">
|
||||
<MenuItem Header="ROM _Info" Command="{Binding ViewROMInfoCommand}" IsEnabled="{Binding IsROMLoaded}"/>
|
||||
<MenuItem Header="_Variables" Command="{Binding ViewVariablesCommand}" IsEnabled="{Binding IsDecompiled}"/>
|
||||
<MenuItem Header="_Functions" Command="{Binding ViewFunctionsCommand}" IsEnabled="{Binding IsDecompiled}"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Help">
|
||||
<MenuItem Header="_About" Command="{Binding AboutCommand}"/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="250"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="Project Explorer" Margin="5" FontWeight="Bold"/>
|
||||
|
||||
<TreeView Grid.Row="1" ItemsSource="{Binding ProjectItems}"
|
||||
SelectedItemChanged="TreeView_SelectedItemChanged">
|
||||
<TreeView.ItemTemplate>
|
||||
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="{Binding Icon}" Width="16" Height="16" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Binding Name}"/>
|
||||
</StackPanel>
|
||||
</HierarchicalDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
<TreeView.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type TreeViewItem}">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||
</Style>
|
||||
</TreeView.ItemContainerStyle>
|
||||
</TreeView>
|
||||
</Grid>
|
||||
<!--Style="{StaticResource DarkModeToggleSwitch}"-->
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TabControl Grid.Row="1" x:Name="mainTabControl">
|
||||
<TabItem Header="Welcome" IsSelected="True" Background="{DynamicResource TertiaryBackgroundBrush}" Foreground="{DynamicResource PrimaryForegroundBrush}">
|
||||
<Grid Background="{DynamicResource SecondaryBackgroundBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="NES Decompiler" FontSize="24" FontWeight="Bold" HorizontalAlignment="Center" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
<TextBlock Grid.Row="1" Text="Static Decompiler for NES/6502 ROMs" FontSize="16" Margin="0,10,0,30" HorizontalAlignment="Center" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
|
||||
<!-- Theme Toggle -->
|
||||
<!--<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,20">
|
||||
<TextBlock Text="Light" VerticalAlignment="Center" Margin="0,0,10,0" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
<ToggleButton
|
||||
IsChecked="{Binding IsDarkTheme}"
|
||||
Command="{Binding ToggleThemeCommand}"
|
||||
CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="Dark" VerticalAlignment="Center" Margin="10,0,0,0" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
</StackPanel>-->
|
||||
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,20">
|
||||
<Button Content="New Project" Width="150" Height="40" Command="{Binding NewProjectCommand}" Margin="0,0,20,0" Background="{DynamicResource TertiaryBackgroundBrush}" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
<Button Content="Open Project..." Width="150" Height="40" Command="{Binding OpenProjectCommand}" Margin="0,0,20,0" Background="{DynamicResource TertiaryBackgroundBrush}" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
<Button Content="Open ROM..." Width="150" Height="40" Command="{Binding OpenROMCommand}" Background="{DynamicResource TertiaryBackgroundBrush}" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0" Margin="10">
|
||||
<TextBlock Text="Recent Projects:" FontWeight="Bold" Margin="0,0,0,10" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
<ListView Height="200" Width="350" ItemsSource="{Binding RecentProjectsList}"
|
||||
MouseDoubleClick="RecentProjects_MouseDoubleClick"
|
||||
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
BorderBrush="{DynamicResource BorderBrush}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
<TextBlock Text="{Binding FilePath}" FontSize="10" Foreground="{DynamicResource SecondaryForegroundBrush}"/>
|
||||
<TextBlock Text="{Binding LastOpened, StringFormat='Last opened: {0}'}" FontSize="10" Foreground="{DynamicResource SecondaryForegroundBrush}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Margin="10">
|
||||
<TextBlock Text="Recent Workspaces:" FontWeight="Bold" Margin="0,0,0,10" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
<ListView Height="200" Width="350" ItemsSource="{Binding RecentWorkspaces}"
|
||||
MouseDoubleClick="RecentWorkspaces_MouseDoubleClick"
|
||||
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
BorderBrush="{DynamicResource BorderBrush}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="5" Text="Recent Files:" FontWeight="Bold" Margin="10,20,0,10" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
<ListBox Grid.Row="5" ItemsSource="{Binding RecentFiles}" Height="100" Margin="10,50,10,10"
|
||||
MouseDoubleClick="RecentFiles_MouseDoubleClick"
|
||||
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
BorderBrush="{DynamicResource BorderBrush}"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Disassembly" Visibility="{Binding IsDisassembled, Converter={StaticResource BoolToVis}}">
|
||||
<Grid>
|
||||
<avalonEdit:TextEditor x:Name="disassemblyEditor"
|
||||
FontFamily="Consolas"
|
||||
FontSize="12"
|
||||
IsReadOnly="True"
|
||||
ShowLineNumbers="True"
|
||||
WordWrap="False">
|
||||
</avalonEdit:TextEditor>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="C Code" Visibility="{Binding IsDecompiled, Converter={StaticResource BoolToVis}}">
|
||||
<Grid>
|
||||
<avalonEdit:TextEditor x:Name="cCodeEditor"
|
||||
FontFamily="Consolas"
|
||||
FontSize="12"
|
||||
IsReadOnly="True"
|
||||
ShowLineNumbers="True"
|
||||
WordWrap="False">
|
||||
</avalonEdit:TextEditor>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Header" Visibility="{Binding IsDecompiled, Converter={StaticResource BoolToVis}}">
|
||||
<Grid>
|
||||
<avalonEdit:TextEditor x:Name="headerEditor"
|
||||
FontFamily="Consolas"
|
||||
FontSize="12"
|
||||
IsReadOnly="True"
|
||||
ShowLineNumbers="True"
|
||||
WordWrap="False">
|
||||
</avalonEdit:TextEditor>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Variables" Name="VariablesTab" Visibility="{Binding IsDecompiled, Converter={StaticResource BoolToVis}}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ToolBar Grid.Row="0">
|
||||
<Button Content="Refresh" Command="{Binding ViewVariablesCommand}"/>
|
||||
<Separator/>
|
||||
<Grid Width="200" Margin="5,0">
|
||||
<TextBox Text="{Binding FunctionSearchText, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock IsHitTestVisible="False" Text="Search functions..." Foreground="Gray" Margin="5,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding FunctionSearchText}" Value="">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<Button Content="Search" Command="{Binding SearchVariablesCommand}"/>
|
||||
</ToolBar>
|
||||
|
||||
<DataGrid Grid.Row="1"
|
||||
ItemsSource="{Binding VariableList}"
|
||||
SelectedItem="{Binding SelectedVariable}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserSortColumns="True"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
BorderBrush="{DynamicResource BorderBrush}"
|
||||
RowBackground="{DynamicResource SecondaryBackgroundBrush}"
|
||||
AlternatingRowBackground="{DynamicResource TertiaryBackgroundBrush}"
|
||||
HorizontalGridLinesBrush="{DynamicResource BorderBrush}"
|
||||
VerticalGridLinesBrush="{DynamicResource BorderBrush}">
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Jump to Definition" Command="{Binding JumpToVariableDefinitionCommand}"/>
|
||||
<MenuItem Header="Find All References" Command="{Binding FindVariableReferencesCommand}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="Rename Variable" Command="{Binding RenameVariableCommand}"/>
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Address" Binding="{Binding AddressHex}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="False"/>
|
||||
<DataGridTextColumn Header="Type" Binding="{Binding TypeName}" IsReadOnly="False"/>
|
||||
<DataGridTextColumn Header="Size" Binding="{Binding Size}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Access" Binding="{Binding AccessType}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Description" Binding="{Binding Description}" IsReadOnly="False" Width="*"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Functions" Name="FunctionsTab" Visibility="{Binding IsDecompiled, Converter={StaticResource BoolToVis}}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ToolBar Grid.Row="0">
|
||||
<Button Content="Refresh" Command="{Binding ViewFunctionsCommand}"/>
|
||||
<Separator/>
|
||||
<Grid Width="200" Margin="5,0">
|
||||
<TextBox Text="{Binding VariableSearchText, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock IsHitTestVisible="False" Text="Search variables..." Foreground="Gray" Margin="5,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding VariableSearchText}" Value="">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<Button Content="Search" Command="{Binding SearchFunctionsCommand}"/>
|
||||
</ToolBar>
|
||||
|
||||
<DataGrid Grid.Row="1"
|
||||
ItemsSource="{Binding FunctionList}"
|
||||
SelectedItem="{Binding SelectedFunction}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserSortColumns="True"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
BorderBrush="{DynamicResource BorderBrush}"
|
||||
RowBackground="{DynamicResource SecondaryBackgroundBrush}"
|
||||
AlternatingRowBackground="{DynamicResource TertiaryBackgroundBrush}"
|
||||
HorizontalGridLinesBrush="{DynamicResource BorderBrush}"
|
||||
VerticalGridLinesBrush="{DynamicResource BorderBrush}">
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Jump to Definition" Command="{Binding JumpToFunctionDefinitionCommand}"/>
|
||||
<MenuItem Header="Find All References" Command="{Binding FindFunctionReferencesCommand}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="Rename Function" Command="{Binding RenameFunctionCommand}"/>
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Address" Binding="{Binding AddressHex}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="False"/>
|
||||
<DataGridTextColumn Header="Instructions" Binding="{Binding InstructionCount}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Variables" Binding="{Binding VariableCount}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Called Functions" Binding="{Binding CalledFunctionCount}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Description" Binding="{Binding Description}" IsReadOnly="False" Width="*"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<StatusBar Grid.Row="2">
|
||||
<StatusBarItem>
|
||||
<TextBlock Text="{Binding StatusText}"/>
|
||||
</StatusBarItem>
|
||||
<Separator/>
|
||||
<StatusBarItem>
|
||||
<ProgressBar Width="100" Height="15" IsIndeterminate="{Binding IsProcessing}"/>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
|
||||
<Grid Grid.Row="1" Background="#80000000"
|
||||
Visibility="{Binding IsProcessing, Converter={StaticResource BoolToVis}}">
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding ProcessingText}" Foreground="White" FontSize="16" Margin="0,0,0,10"/>
|
||||
<ProgressBar Width="300" Height="20" IsIndeterminate="True"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,305 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.ComponentModel;
|
||||
using NESDecompiler.GUI.ViewModels;
|
||||
using ICSharpCode.AvalonEdit.Highlighting;
|
||||
using System.Xml;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
|
||||
using static NESDecompiler.GUI.ViewModels.ProjectItemViewModel;
|
||||
using System.Windows.Media;
|
||||
using NESDecompiler.GUI.Themes;
|
||||
using System.Collections.Generic;
|
||||
using ICSharpCode.AvalonEdit;
|
||||
|
||||
namespace NESDecompiler.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private MainViewModel viewModel;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
viewModel = new MainViewModel();
|
||||
DataContext = viewModel;
|
||||
|
||||
LoadSyntaxHighlighting();
|
||||
|
||||
Loaded += MainWindow_Loaded;
|
||||
Closing += MainWindow_Closing;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads custom syntax highlighting for ASM 6502
|
||||
/// </summary>
|
||||
private void LoadSyntaxHighlighting()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("NESDecompiler.GUI.Resources.ASM6502.xshd"))
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
using (var reader = new XmlTextReader(stream))
|
||||
{
|
||||
var highlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance);
|
||||
HighlightingManager.Instance.RegisterHighlighting("ASM6502", new[] { ".asm" }, highlighting);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Failed to load syntax highlighting: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
viewModel.Initialize();
|
||||
|
||||
if (viewModel is INotifyPropertyChanged notifyViewModel)
|
||||
{
|
||||
notifyViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
}
|
||||
|
||||
cCodeEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");
|
||||
headerEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");
|
||||
|
||||
try
|
||||
{
|
||||
disassemblyEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("ASM6502");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
disassemblyEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("XML");
|
||||
}
|
||||
ThemeManager.Instance.ThemeChanged += (s, args) => ApplyThemeToEditors();
|
||||
ApplyThemeToEditors();
|
||||
SetupEditorEvents();
|
||||
}
|
||||
|
||||
private void ApplyThemeToEditors()
|
||||
{
|
||||
bool isDarkTheme = ThemeManager.Instance.IsDarkTheme;
|
||||
|
||||
SolidColorBrush backgroundBrush = Application.Current.Resources["SecondaryBackgroundBrush"] as SolidColorBrush;
|
||||
SolidColorBrush foregroundBrush = Application.Current.Resources["PrimaryForegroundBrush"] as SolidColorBrush;
|
||||
|
||||
if (backgroundBrush != null && foregroundBrush != null)
|
||||
{
|
||||
ApplyThemeToEditor(disassemblyEditor, backgroundBrush, foregroundBrush);
|
||||
ApplyThemeToEditor(cCodeEditor, backgroundBrush, foregroundBrush);
|
||||
ApplyThemeToEditor(headerEditor, backgroundBrush, foregroundBrush);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyThemeToEditor(TextEditor editor, SolidColorBrush background, SolidColorBrush foreground)
|
||||
{
|
||||
if (editor != null)
|
||||
{
|
||||
editor.Background = background;
|
||||
editor.Foreground = foreground;
|
||||
}
|
||||
}
|
||||
|
||||
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(MainViewModel.DisassemblyText))
|
||||
{
|
||||
disassemblyEditor.Text = viewModel.DisassemblyText;
|
||||
}
|
||||
else if (e.PropertyName == nameof(MainViewModel.CCodeText))
|
||||
{
|
||||
cCodeEditor.Text = viewModel.CCodeText;
|
||||
}
|
||||
else if (e.PropertyName == nameof(MainViewModel.HeaderText))
|
||||
{
|
||||
headerEditor.Text = viewModel.HeaderText;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
if (viewModel is INotifyPropertyChanged notifyViewModel)
|
||||
{
|
||||
notifyViewModel.PropertyChanged -= ViewModel_PropertyChanged;
|
||||
}
|
||||
|
||||
if (viewModel.HasUnsavedChanges)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"You have unsaved changes. Do you want to save before exiting?",
|
||||
"Save Changes",
|
||||
MessageBoxButton.YesNoCancel,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.Cancel)
|
||||
{
|
||||
e.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
viewModel.SaveAllCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
{
|
||||
var selectedItem = e.NewValue as ProjectItemViewModel;
|
||||
|
||||
var previousSelection = viewModel.SelectedProjectItem;
|
||||
|
||||
viewModel.SelectedProjectItem = selectedItem;
|
||||
|
||||
if (selectedItem != null)
|
||||
{
|
||||
if (sender is TreeView treeView)
|
||||
{
|
||||
if (selectedItem.Tag is ROMProject currentRom && previousSelection?.Tag is ROMProject prevRom)
|
||||
{
|
||||
currentRom.IsExpanded = true;
|
||||
prevRom.IsExpanded = true;
|
||||
|
||||
KeepTreeViewItemsExpanded(treeView);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedItem.Tag is ROMProject selectedRom)
|
||||
{
|
||||
if (viewModel.ActiveROM != selectedRom)
|
||||
{
|
||||
viewModel.ActiveROM = selectedRom;
|
||||
}
|
||||
}
|
||||
|
||||
switch (selectedItem.Type)
|
||||
{
|
||||
case ProjectItemType.Disassembly:
|
||||
ActivateTab("Disassembly");
|
||||
break;
|
||||
case ProjectItemType.CCode:
|
||||
ActivateTab("C Code");
|
||||
break;
|
||||
case ProjectItemType.Header:
|
||||
ActivateTab("Header");
|
||||
break;
|
||||
case ProjectItemType.Variables:
|
||||
viewModel.ViewVariablesCommand.Execute(null);
|
||||
break;
|
||||
case ProjectItemType.Functions:
|
||||
viewModel.ViewFunctionsCommand.Execute(null);
|
||||
break;
|
||||
case ProjectItemType.ROMInfo:
|
||||
viewModel.ViewROMInfoCommand.Execute(null);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void KeepTreeViewItemsExpanded(TreeView treeView)
|
||||
{
|
||||
var expandedItems = FindExpandedItems(treeView);
|
||||
|
||||
foreach (var item in expandedItems)
|
||||
{
|
||||
item.IsExpanded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private List<TreeViewItem> FindExpandedItems(DependencyObject parent)
|
||||
{
|
||||
var result = new List<TreeViewItem>();
|
||||
|
||||
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(parent, i);
|
||||
|
||||
if (child is TreeViewItem item && item.IsExpanded)
|
||||
{
|
||||
result.Add(item);
|
||||
}
|
||||
|
||||
result.AddRange(FindExpandedItems(child));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void RecentFiles_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is ListBox listBox && listBox.SelectedItem is string filePath)
|
||||
{
|
||||
viewModel.OpenRecentFile(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecentProjects_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is ListView listView && listView.SelectedItem is RecentProjectInfo projectInfo)
|
||||
{
|
||||
viewModel.OpenRecentProject(projectInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecentWorkspaces_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is ListView listView && listView.SelectedItem is string filePath)
|
||||
{
|
||||
viewModel.OpenRecentWorkspace(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
public void ActivateTab(string tabName)
|
||||
{
|
||||
foreach (TabItem tab in mainTabControl.Items)
|
||||
{
|
||||
if (tab.Name == tabName + "Tab" || tab.Header.ToString() == tabName)
|
||||
{
|
||||
tab.IsSelected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set up text change event handlers for editor controls
|
||||
/// </summary>
|
||||
private void SetupEditorEvents()
|
||||
{
|
||||
disassemblyEditor.TextChanged += (s, e) => {
|
||||
if (viewModel.DisassemblyText != disassemblyEditor.Text)
|
||||
{
|
||||
viewModel.DisassemblyText = disassemblyEditor.Text;
|
||||
}
|
||||
};
|
||||
|
||||
cCodeEditor.TextChanged += (s, e) => {
|
||||
if (viewModel.CCodeText != cCodeEditor.Text)
|
||||
{
|
||||
viewModel.CCodeText = cCodeEditor.Text;
|
||||
}
|
||||
};
|
||||
|
||||
headerEditor.TextChanged += (s, e) => {
|
||||
if (viewModel.HeaderText != headerEditor.Text)
|
||||
{
|
||||
viewModel.HeaderText = headerEditor.Text;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using NESDecompiler.Core.Decompilation;
|
||||
using NESDecompiler.Core.ROM;
|
||||
|
||||
public class Project
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FilePath { get; set; }
|
||||
public List<ROMProject> ROMs { get; set; } = new List<ROMProject>();
|
||||
public DateTime LastModified { get; set; }
|
||||
}
|
||||
|
||||
public class ROMProject
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FilePath { get; set; }
|
||||
public bool IsDisassembled { get; set; }
|
||||
public bool IsDecompiled { get; set; }
|
||||
public ROMInfo ROMInfo { get; set; }
|
||||
public string DisassemblyText { get; set; } = string.Empty;
|
||||
public string CCodeText { get; set; } = string.Empty;
|
||||
public string HeaderText { get; set; } = string.Empty;
|
||||
public Dictionary<string, VariableWorkspaceData> Variables { get; set; } = new Dictionary<string, VariableWorkspaceData>();
|
||||
public Dictionary<string, FunctionWorkspaceData> Functions { get; set; } = new Dictionary<string, FunctionWorkspaceData>();
|
||||
public bool IsExpanded { get; set; } = true;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\ASM6502.xshd" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Remove="Resources\Themes\DarkTheme.xaml" />
|
||||
<Page Remove="Resources\Themes\LightTheme.xaml" />
|
||||
<Page Remove="Resources\Themes\ThemeManager.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AvalonEdit" Version="6.2.0.78" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NESDecompiler.Core\NESDecompiler.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\ASM6502.xshd">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Themes\DarkTheme.xaml">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\Themes\LightTheme.xaml">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\Themes\ThemeManager.xaml">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0"?>
|
||||
<SyntaxDefinition name="ASM6502" extensions=".asm" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
|
||||
<Color name="Comment" foreground="#008000" />
|
||||
<Color name="String" foreground="#A31515" />
|
||||
<Color name="Directive" foreground="#00FCF8" fontWeight="bold" />
|
||||
<Color name="Instruction" foreground="#009BFC" />
|
||||
<Color name="Register" foreground="#800080" />
|
||||
<Color name="Label" foreground="#2B91AF" />
|
||||
<Color name="Number" foreground="#FF8000" />
|
||||
<Color name="Address" foreground="#FF0000" />
|
||||
|
||||
<RuleSet>
|
||||
<!-- Comments -->
|
||||
<Span color="Comment" begin=";" />
|
||||
<Span color="Comment" begin="//" />
|
||||
<Span color="Comment" multiline="true" begin="/\*" end="\*/" />
|
||||
|
||||
<!-- Strings -->
|
||||
<Span color="String" begin=""" end=""" />
|
||||
<Span color="String" begin="'" end="'" />
|
||||
|
||||
<!-- Directives -->
|
||||
<Rule color="Directive">
|
||||
\.(org|byte|word|db|dw|data|text|ascii|asciiz|define|include|macro|endm|segment|end)
|
||||
</Rule>
|
||||
|
||||
<!-- Instructions -->
|
||||
<Rule color="Instruction">
|
||||
\b(adc|and|asl|bcc|bcs|beq|bit|bmi|bne|bpl|brk|bvc|bvs|clc|cld|cli|clv|cmp|cpx|cpy|dec|dex|dey|eor|inc|inx|iny|jmp|jsr|lda|ldx|ldy|lsr|nop|ora|pha|php|pla|plp|rol|ror|rti|rts|sbc|sec|sed|sei|sta|stx|sty|tax|tay|tsx|txa|txs|tya)\b
|
||||
</Rule>
|
||||
|
||||
<!-- Registers -->
|
||||
<Rule color="Register">
|
||||
\b(a|x|y|sp)\b
|
||||
</Rule>
|
||||
|
||||
<!-- Labels -->
|
||||
<Rule color="Label">
|
||||
^[a-zA-Z_][a-zA-Z0-9_]*:
|
||||
</Rule>
|
||||
|
||||
<!-- Numbers -->
|
||||
<Rule color="Number">
|
||||
\b([$%]?[0-9A-Fa-f]+)\b
|
||||
</Rule>
|
||||
|
||||
<!-- Addresses -->
|
||||
<Rule color="Address">
|
||||
\$[0-9A-Fa-f]+
|
||||
</Rule>
|
||||
</RuleSet>
|
||||
</SyntaxDefinition>
|
||||
@@ -0,0 +1,236 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<!-- Base Colors -->
|
||||
<Color x:Key="PrimaryBackgroundColor">#1E1E1E</Color>
|
||||
<Color x:Key="SecondaryBackgroundColor">#252526</Color>
|
||||
<Color x:Key="TertiaryBackgroundColor">#2D2D30</Color>
|
||||
<Color x:Key="PrimaryForegroundColor">#FFFFFF</Color>
|
||||
<Color x:Key="SecondaryForegroundColor">#B6B6B6</Color>
|
||||
<Color x:Key="AccentColor">#007ACC</Color>
|
||||
<Color x:Key="BorderColor">#3F3F46</Color>
|
||||
|
||||
<!-- Brushes -->
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="{StaticResource PrimaryBackgroundColor}"/>
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="{StaticResource SecondaryBackgroundColor}"/>
|
||||
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="{StaticResource TertiaryBackgroundColor}"/>
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="{StaticResource PrimaryForegroundColor}"/>
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="{StaticResource SecondaryForegroundColor}"/>
|
||||
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}"/>
|
||||
<SolidColorBrush x:Key="BorderBrush" Color="{StaticResource BorderColor}"/>
|
||||
|
||||
<!-- Control Styles -->
|
||||
<Style TargetType="{x:Type Window}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="CaretBrush" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type Button}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ComboBox}">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ListBox}">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ListBoxItem}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ListView}">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ListViewItem}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TreeView}">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TreeViewItem}">
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TabControl}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TabItem}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TabItem}">
|
||||
<Border Name="Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="1,1,1,0"
|
||||
Margin="2,0">
|
||||
<ContentPresenter x:Name="ContentSite"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
ContentSource="Header"
|
||||
Margin="10,2"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="False">
|
||||
<Setter TargetName="Border" Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource SecondaryForegroundBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type DataGrid}">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="RowBackground" Value="{StaticResource SecondaryBackgroundBrush}"/>
|
||||
<Setter Property="AlternatingRowBackground" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="VerticalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type DataGridRow}">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type DataGridColumnHeader}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type DataGridCell}">
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type Menu}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type MenuItem}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type StatusBar}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ScrollBar}">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ToolBar}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type Grid}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<!--<Style x:Key="DarkModeToggleSwitch" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Grid>
|
||||
<Border x:Name="BackgroundBorder"
|
||||
Background="#333333"
|
||||
CornerRadius="10"
|
||||
Width="50"
|
||||
Height="24"/>
|
||||
<Border x:Name="SwitchThumb"
|
||||
Background="White"
|
||||
CornerRadius="10"
|
||||
Width="20"
|
||||
Height="20"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="2,0,0,0">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="1" BlurRadius="2" Opacity="0.3"/>
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="BackgroundBorder" Property="Background" Value="#0078D7"/>
|
||||
<Setter TargetName="SwitchThumb" Property="Margin" Value="28,0,0,0"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>-->
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,239 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<!-- Base Colors -->
|
||||
<Color x:Key="PrimaryBackgroundColor">#FFFFFF</Color>
|
||||
<Color x:Key="SecondaryBackgroundColor">#F0F0F0</Color>
|
||||
<Color x:Key="TertiaryBackgroundColor">#E6E6E6</Color>
|
||||
<Color x:Key="PrimaryForegroundColor">#000000</Color>
|
||||
<Color x:Key="SecondaryForegroundColor">#555555</Color>
|
||||
<Color x:Key="AccentColor">#0078D7</Color>
|
||||
<Color x:Key="BorderColor">#CCCCCC</Color>
|
||||
|
||||
<!-- Brushes -->
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="{StaticResource PrimaryBackgroundColor}"/>
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="{StaticResource SecondaryBackgroundColor}"/>
|
||||
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="{StaticResource TertiaryBackgroundColor}"/>
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="{StaticResource PrimaryForegroundColor}"/>
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="{StaticResource SecondaryForegroundColor}"/>
|
||||
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}"/>
|
||||
<SolidColorBrush x:Key="BorderBrush" Color="{StaticResource BorderColor}"/>
|
||||
|
||||
<!-- Control Styles -->
|
||||
<Style TargetType="{x:Type Window}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="CaretBrush" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type Button}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ComboBox}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ListBox}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ListBoxItem}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ListView}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ListViewItem}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TreeView}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TreeViewItem}">
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TabControl}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TabItem}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TabItem}">
|
||||
<Border Name="Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="1,1,1,0"
|
||||
Margin="2,0">
|
||||
<ContentPresenter x:Name="ContentSite"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
ContentSource="Header"
|
||||
Margin="10,2"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="False">
|
||||
<Setter TargetName="Border" Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource SecondaryForegroundBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type DataGrid}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="RowBackground" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="AlternatingRowBackground" Value="{StaticResource SecondaryBackgroundBrush}"/>
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="VerticalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type DataGridRow}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type DataGridColumnHeader}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type DataGridCell}">
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type Menu}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type MenuItem}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type StatusBar}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ScrollBar}">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ToolBar}">
|
||||
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type Grid}">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
|
||||
</Style>
|
||||
|
||||
<!-- Custom ToggleSwitch Style -->
|
||||
<Style x:Key="DarkModeToggleSwitch" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Grid>
|
||||
<Border x:Name="BackgroundBorder"
|
||||
Background="#CCCCCC"
|
||||
CornerRadius="10"
|
||||
Width="50"
|
||||
Height="24"/>
|
||||
<Border x:Name="SwitchThumb"
|
||||
Background="White"
|
||||
CornerRadius="10"
|
||||
Width="20"
|
||||
Height="20"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="2,0,0,0">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="1" BlurRadius="2" Opacity="0.3"/>
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="BackgroundBorder" Property="Background" Value="#0078D7"/>
|
||||
<Setter TargetName="SwitchThumb" Property="Margin" Value="28,0,0,0"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,8 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Themes/DarkTheme.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace NESDecompiler.GUI.Themes
|
||||
{
|
||||
public class ThemeManager
|
||||
{
|
||||
private static ThemeManager _instance;
|
||||
public static ThemeManager Instance => _instance ??= new ThemeManager();
|
||||
|
||||
private readonly Dictionary<string, object> _themeResources = new Dictionary<string, object>();
|
||||
|
||||
private ThemeManager()
|
||||
{
|
||||
IsDarkTheme = true;
|
||||
}
|
||||
|
||||
public bool IsDarkTheme { get; private set; }
|
||||
|
||||
public void ToggleTheme()
|
||||
{
|
||||
IsDarkTheme = !IsDarkTheme;
|
||||
ApplyTheme();
|
||||
}
|
||||
|
||||
public void SetTheme(bool isDarkTheme)
|
||||
{
|
||||
if (IsDarkTheme != isDarkTheme)
|
||||
{
|
||||
IsDarkTheme = isDarkTheme;
|
||||
ApplyTheme();
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
var appResources = Application.Current.Resources.MergedDictionaries;
|
||||
|
||||
_themeResources.Clear();
|
||||
|
||||
for (int i = appResources.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var resourceUri = appResources[i].Source?.ToString();
|
||||
if (resourceUri != null &&
|
||||
(resourceUri.Contains("DarkTheme") || resourceUri.Contains("LightTheme")))
|
||||
{
|
||||
appResources.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
var themeDictionary = new ResourceDictionary
|
||||
{
|
||||
Source = new Uri(
|
||||
$"pack://application:,,,/Resources/Themes/{(IsDarkTheme ? "Dark" : "Light")}Theme.xaml",
|
||||
UriKind.Absolute)
|
||||
};
|
||||
|
||||
appResources.Insert(0, themeDictionary);
|
||||
|
||||
UpdateResourcesExplicitly(themeDictionary);
|
||||
|
||||
ThemeChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Error applying theme: {ex.Message}", "Theme Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateResourcesExplicitly(ResourceDictionary themeDictionary)
|
||||
{
|
||||
UpdateResourceIfExists(themeDictionary, "PrimaryBackgroundBrush");
|
||||
UpdateResourceIfExists(themeDictionary, "SecondaryBackgroundBrush");
|
||||
UpdateResourceIfExists(themeDictionary, "TertiaryBackgroundBrush");
|
||||
UpdateResourceIfExists(themeDictionary, "PrimaryForegroundBrush");
|
||||
UpdateResourceIfExists(themeDictionary, "SecondaryForegroundBrush");
|
||||
UpdateResourceIfExists(themeDictionary, "AccentBrush");
|
||||
UpdateResourceIfExists(themeDictionary, "BorderBrush");
|
||||
}
|
||||
|
||||
private void UpdateResourceIfExists(ResourceDictionary dictionary, string key)
|
||||
{
|
||||
if (dictionary.Contains(key))
|
||||
{
|
||||
_themeResources[key] = dictionary[key];
|
||||
|
||||
Application.Current.Resources[key] = dictionary[key];
|
||||
}
|
||||
}
|
||||
|
||||
public object GetResource(string key)
|
||||
{
|
||||
if (_themeResources.TryGetValue(key, out var resource))
|
||||
{
|
||||
return resource;
|
||||
}
|
||||
|
||||
return Application.Current.Resources[key];
|
||||
}
|
||||
|
||||
public event EventHandler ThemeChanged;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace NESDecompiler.GUI.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Types of project items
|
||||
/// </summary>
|
||||
public enum ProjectItemType
|
||||
{
|
||||
Project,
|
||||
ROM,
|
||||
ROMInfo,
|
||||
Disassembly,
|
||||
CCode,
|
||||
Header,
|
||||
Variables,
|
||||
Functions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// View model for a project item in the tree view
|
||||
/// </summary>
|
||||
public class ProjectItemViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private string name = "";
|
||||
private ProjectItemType type;
|
||||
private string path = "";
|
||||
|
||||
/// <summary>
|
||||
/// The name of this item
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => name;
|
||||
set
|
||||
{
|
||||
name = value;
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
}
|
||||
|
||||
public class RecentProjectInfo
|
||||
{
|
||||
public string FilePath { get; set; }
|
||||
public string Name { get; set; }
|
||||
public DateTime LastOpened { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of this item
|
||||
/// </summary>
|
||||
public ProjectItemType Type
|
||||
{
|
||||
get => type;
|
||||
set
|
||||
{
|
||||
type = value;
|
||||
OnPropertyChanged(nameof(Type));
|
||||
OnPropertyChanged(nameof(Icon));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The file path of this item
|
||||
/// </summary>
|
||||
public string Path
|
||||
{
|
||||
get => path;
|
||||
set
|
||||
{
|
||||
path = value;
|
||||
OnPropertyChanged(nameof(Path));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The icon for this item type
|
||||
/// </summary>
|
||||
public string Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
return Type switch
|
||||
{
|
||||
ProjectItemType.ROM => "/Resources/Icons/rom.png",
|
||||
ProjectItemType.ROMInfo => "/Resources/Icons/info.png",
|
||||
ProjectItemType.Disassembly => "/Resources/Icons/asm.png",
|
||||
ProjectItemType.CCode => "/Resources/Icons/c.png",
|
||||
ProjectItemType.Header => "/Resources/Icons/h.png",
|
||||
ProjectItemType.Variables => "/Resources/Icons/var.png",
|
||||
ProjectItemType.Functions => "/Resources/Icons/func.png",
|
||||
_ => "/Resources/Icons/file.png"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Child items for this item
|
||||
/// </summary>
|
||||
public ObservableCollection<ProjectItemViewModel> Children { get; } = new ObservableCollection<ProjectItemViewModel>();
|
||||
|
||||
private bool isExpanded = false;
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => isExpanded;
|
||||
set
|
||||
{
|
||||
isExpanded = value;
|
||||
OnPropertyChanged(nameof(IsExpanded));
|
||||
|
||||
if (Tag is ROMProject romProject)
|
||||
{
|
||||
romProject.IsExpanded = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public object Tag { get; set; }
|
||||
|
||||
#region INotifyPropertyChanged
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
public class VariableViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private ushort address;
|
||||
private string name;
|
||||
private string typeName;
|
||||
private int size;
|
||||
private bool isRead;
|
||||
private bool isWritten;
|
||||
private string description;
|
||||
|
||||
public ushort Address
|
||||
{
|
||||
get => address;
|
||||
set { address = value; OnPropertyChanged(nameof(Address)); OnPropertyChanged(nameof(AddressHex)); }
|
||||
}
|
||||
|
||||
public string AddressHex => $"0x{Address:X4}";
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => name;
|
||||
set { name = value; OnPropertyChanged(nameof(Name)); }
|
||||
}
|
||||
|
||||
public string TypeName
|
||||
{
|
||||
get => typeName;
|
||||
set { typeName = value; OnPropertyChanged(nameof(TypeName)); }
|
||||
}
|
||||
|
||||
public int Size
|
||||
{
|
||||
get => size;
|
||||
set { size = value; OnPropertyChanged(nameof(Size)); }
|
||||
}
|
||||
|
||||
public bool IsRead
|
||||
{
|
||||
get => isRead;
|
||||
set { isRead = value; OnPropertyChanged(nameof(IsRead)); OnPropertyChanged(nameof(AccessType)); }
|
||||
}
|
||||
|
||||
public bool IsWritten
|
||||
{
|
||||
get => isWritten;
|
||||
set { isWritten = value; OnPropertyChanged(nameof(IsWritten)); OnPropertyChanged(nameof(AccessType)); }
|
||||
}
|
||||
|
||||
public string AccessType => $"{(IsRead ? "Read" : "")}{(IsRead && IsWritten ? "/" : "")}{(IsWritten ? "Write" : "")}";
|
||||
|
||||
public string Description
|
||||
{
|
||||
get => description;
|
||||
set { description = value; OnPropertyChanged(nameof(Description)); }
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
public class FunctionViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private ushort address;
|
||||
private string name;
|
||||
private int instructionCount;
|
||||
private int variableCount;
|
||||
private int calledFunctionCount;
|
||||
private List<string> parameters = new List<string>();
|
||||
private string description;
|
||||
|
||||
public ushort Address
|
||||
{
|
||||
get => address;
|
||||
set { address = value; OnPropertyChanged(nameof(Address)); OnPropertyChanged(nameof(AddressHex)); }
|
||||
}
|
||||
|
||||
public string AddressHex => $"0x{Address:X4}";
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => name;
|
||||
set { name = value; OnPropertyChanged(nameof(Name)); }
|
||||
}
|
||||
|
||||
public int InstructionCount
|
||||
{
|
||||
get => instructionCount;
|
||||
set { instructionCount = value; OnPropertyChanged(nameof(InstructionCount)); }
|
||||
}
|
||||
|
||||
public int VariableCount
|
||||
{
|
||||
get => variableCount;
|
||||
set { variableCount = value; OnPropertyChanged(nameof(VariableCount)); }
|
||||
}
|
||||
|
||||
public int CalledFunctionCount
|
||||
{
|
||||
get => calledFunctionCount;
|
||||
set { calledFunctionCount = value; OnPropertyChanged(nameof(CalledFunctionCount)); }
|
||||
}
|
||||
|
||||
public List<string> Parameters
|
||||
{
|
||||
get => parameters;
|
||||
set { parameters = value; OnPropertyChanged(nameof(Parameters)); }
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get => description;
|
||||
set { description = value; OnPropertyChanged(nameof(Description)); }
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,934 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using Microsoft.Win32;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NESDecompiler.GUI.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for exporting to Visual Studio 2022 solution
|
||||
/// </summary>
|
||||
public class VisualStudioExporter
|
||||
{
|
||||
private readonly string solutionName;
|
||||
private readonly string rootPath;
|
||||
private readonly string cCode;
|
||||
private readonly string headerCode;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Visual Studio exporter
|
||||
/// </summary>
|
||||
/// <param name="solutionName">The name of the solution</param>
|
||||
/// <param name="rootPath">The root directory path</param>
|
||||
/// <param name="cCode">The C code to export</param>
|
||||
/// <param name="headerCode">The header code to export</param>
|
||||
public VisualStudioExporter(string solutionName, string rootPath, string cCode, string headerCode)
|
||||
{
|
||||
this.solutionName = solutionName;
|
||||
this.rootPath = rootPath;
|
||||
this.cCode = cCode;
|
||||
this.headerCode = headerCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports the decompiled code as a Visual Studio 2022 solution
|
||||
/// </summary>
|
||||
/// <returns>True if successful, false otherwise</returns>
|
||||
public bool ExportToVisualStudio()
|
||||
{
|
||||
try
|
||||
{
|
||||
string solutionDir = Path.Combine(rootPath, solutionName);
|
||||
Directory.CreateDirectory(solutionDir);
|
||||
|
||||
string sourceDir = Path.Combine(solutionDir, "src");
|
||||
Directory.CreateDirectory(sourceDir);
|
||||
|
||||
File.WriteAllText(Path.Combine(sourceDir, $"{solutionName}.c"), cCode);
|
||||
File.WriteAllText(Path.Combine(sourceDir, $"{solutionName}.h"), headerCode);
|
||||
|
||||
CreateNesPlatformHeader(sourceDir);
|
||||
|
||||
CreateMainFile(sourceDir);
|
||||
|
||||
CreateCMakeFile(solutionDir);
|
||||
|
||||
CreateSolutionFile(solutionDir);
|
||||
|
||||
CreateProjectFile(solutionDir);
|
||||
|
||||
CreateFiltersFile(solutionDir);
|
||||
|
||||
CreateReadmeFile(solutionDir);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Failed to export to Visual Studio: {ex.Message}", "Export Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NES platform header file with platform-specific declarations
|
||||
/// </summary>
|
||||
private void CreateNesPlatformHeader(string sourceDir)
|
||||
{
|
||||
string content = @"#ifndef NES_PLATFORM_H
|
||||
#define NES_PLATFORM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// NES PPU registers enum (for use in switch statements)
|
||||
typedef enum {
|
||||
PPUCTRL_REG = 0x2000,
|
||||
PPUMASK_REG = 0x2001,
|
||||
PPUSTATUS_REG = 0x2002,
|
||||
OAMADDR_REG = 0x2003,
|
||||
OAMDATA_REG = 0x2004,
|
||||
PPUSCROLL_REG = 0x2005,
|
||||
PPUADDR_REG = 0x2006,
|
||||
PPUDATA_REG = 0x2007,
|
||||
OAMDMA_REG = 0x4014
|
||||
} NESRegisters;
|
||||
|
||||
// NES PPU registers (Memory mapped hardware registers)
|
||||
#define PPUCTRL 0x2000
|
||||
#define PPUMASK 0x2001
|
||||
#define PPUSTATUS 0x2002
|
||||
#define OAMADDR 0x2003
|
||||
#define OAMDATA 0x2004
|
||||
#define PPUSCROLL 0x2005
|
||||
#define PPUADDR 0x2006
|
||||
#define PPUDATA 0x2007
|
||||
#define OAMDMA 0x4014
|
||||
|
||||
// NES APU registers
|
||||
#define APU_PULSE1_1 0x4000
|
||||
#define APU_PULSE1_2 0x4001
|
||||
#define APU_PULSE1_3 0x4002
|
||||
#define APU_PULSE1_4 0x4003
|
||||
#define APU_PULSE2_1 0x4004
|
||||
#define APU_PULSE2_2 0x4005
|
||||
#define APU_PULSE2_3 0x4006
|
||||
#define APU_PULSE2_4 0x4007
|
||||
#define APU_TRI_1 0x4008
|
||||
#define APU_TRI_2 0x4009
|
||||
#define APU_TRI_3 0x400A
|
||||
#define APU_TRI_4 0x400B
|
||||
#define APU_NOISE_1 0x400C
|
||||
#define APU_NOISE_2 0x400D
|
||||
#define APU_NOISE_3 0x400E
|
||||
#define APU_NOISE_4 0x400F
|
||||
#define APU_DMC_1 0x4010
|
||||
#define APU_DMC_2 0x4011
|
||||
#define APU_DMC_3 0x4012
|
||||
#define APU_DMC_4 0x4013
|
||||
#define APU_STATUS 0x4015
|
||||
#define APU_FRAMECNT 0x4017
|
||||
|
||||
// NES Joypad registers
|
||||
#define JOY1 0x4016
|
||||
#define JOY2 0x4017
|
||||
|
||||
// CPU Status Flag Masks (same as in the decompiled code)
|
||||
#define CARRY_FLAG 0x01
|
||||
#define ZERO_FLAG 0x02
|
||||
#define INTERRUPT_FLAG 0x04
|
||||
#define DECIMAL_FLAG 0x08
|
||||
#define BREAK_FLAG 0x10
|
||||
#define UNUSED_FLAG 0x20
|
||||
#define OVERFLOW_FLAG 0x40
|
||||
#define NEGATIVE_FLAG 0x80
|
||||
|
||||
// Memory map constants
|
||||
#define RAM_START 0x0000
|
||||
#define RAM_SIZE 0x0800
|
||||
#define RAM_MIRROR_END 0x1FFF
|
||||
#define PPU_START 0x2000
|
||||
#define PPU_SIZE 0x0008
|
||||
#define PPU_MIRROR_END 0x3FFF
|
||||
#define APU_IO_START 0x4000
|
||||
#define APU_IO_END 0x4017
|
||||
#define APU_IO_SIZE (APU_IO_END - APU_IO_START + 1)
|
||||
#define CART_START 0x4020
|
||||
#define CART_END 0xFFFF
|
||||
|
||||
// Platform abstraction layer
|
||||
typedef struct {
|
||||
uint8_t memory[0x10000]; // 64KB of addressable memory
|
||||
uint8_t ppu_memory[0x4000]; // 16KB of PPU memory
|
||||
|
||||
// CPU registers
|
||||
uint8_t a; // Accumulator
|
||||
uint8_t x; // X register
|
||||
uint8_t y; // Y register
|
||||
uint8_t sp; // Stack pointer
|
||||
uint8_t status; // Status register
|
||||
uint16_t pc; // Program counter
|
||||
|
||||
// Input state
|
||||
uint8_t joy1_state;
|
||||
uint8_t joy2_state;
|
||||
|
||||
// Timing
|
||||
uint64_t cycles;
|
||||
|
||||
// Debugger state
|
||||
bool debug_enabled;
|
||||
bool breakpoints[0x10000]; // One per memory address
|
||||
} NESPlatform;
|
||||
|
||||
// Initialize the NES platform
|
||||
void nes_platform_init(NESPlatform* platform);
|
||||
|
||||
// Reset the NES platform
|
||||
void nes_platform_reset(NESPlatform* platform);
|
||||
|
||||
// Read a byte from memory
|
||||
uint8_t nes_platform_read(NESPlatform* platform, uint16_t address);
|
||||
|
||||
// Write a byte to memory
|
||||
void nes_platform_write(NESPlatform* platform, uint16_t address, uint8_t value);
|
||||
|
||||
// Push a byte to the stack
|
||||
void nes_platform_push(NESPlatform* platform, uint8_t value);
|
||||
|
||||
// Pop a byte from the stack
|
||||
uint8_t nes_platform_pop(NESPlatform* platform);
|
||||
|
||||
// Execute a single instruction
|
||||
void nes_platform_execute(NESPlatform* platform);
|
||||
|
||||
// Platform abstraction for running decompiled code
|
||||
void nes_platform_run_decompiled(NESPlatform* platform);
|
||||
|
||||
// Debug functions
|
||||
void nes_platform_dump_memory(NESPlatform* platform, uint16_t start, uint16_t end);
|
||||
void nes_platform_set_breakpoint(NESPlatform* platform, uint16_t address, bool enabled);
|
||||
void nes_platform_dump_cpu_state(NESPlatform* platform);
|
||||
|
||||
#endif
|
||||
";
|
||||
File.WriteAllText(Path.Combine(sourceDir, "nes_platform.h"), content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a main.c file that includes the decompiled code
|
||||
/// </summary>
|
||||
private void CreateMainFile(string sourceDir)
|
||||
{
|
||||
string content = $@"#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include ""nes_platform.h""
|
||||
#include ""{solutionName}.h""
|
||||
|
||||
// Implementation of platform abstraction layer
|
||||
NESPlatform nes;
|
||||
|
||||
// Debug flag
|
||||
bool debug_enabled = false;
|
||||
|
||||
// Read a byte from NES memory with proper memory mapping
|
||||
uint8_t nes_platform_read(NESPlatform* platform, uint16_t address) {{
|
||||
// Apply memory mirroring for RAM
|
||||
if (address < 0x2000) {{
|
||||
// 2KB RAM with 3 mirrors (0x0000-0x07FF repeated 4 times)
|
||||
return platform->memory[address & 0x07FF];
|
||||
}}
|
||||
// PPU registers with mirrors
|
||||
else if (address < 0x4000) {{
|
||||
// 8 bytes mirrored throughout 0x2000-0x3FFF
|
||||
return platform->memory[0x2000 + (address & 0x7)];
|
||||
}}
|
||||
// Direct reads for everything else
|
||||
else {{
|
||||
return platform->memory[address];
|
||||
}}
|
||||
}}
|
||||
|
||||
// Write a byte to NES memory with proper memory mapping
|
||||
void nes_platform_write(NESPlatform* platform, uint16_t address, uint8_t value) {{
|
||||
if (platform->debug_enabled) {{
|
||||
printf(""Write: 0x%04X = 0x%02X\n"", address, value);
|
||||
}}
|
||||
|
||||
// Apply memory mirroring for RAM
|
||||
if (address < 0x2000) {{
|
||||
// 2KB RAM with 3 mirrors (0x0000-0x07FF repeated 4 times)
|
||||
platform->memory[address & 0x07FF] = value;
|
||||
}}
|
||||
// PPU registers with mirrors
|
||||
else if (address < 0x4000) {{
|
||||
// 8 bytes mirrored throughout 0x2000-0x3FFF
|
||||
uint16_t register_addr = 0x2000 + (address & 0x7);
|
||||
platform->memory[register_addr] = value;
|
||||
|
||||
// Special handling for certain PPU registers
|
||||
switch (register_addr) {{
|
||||
case PPUCTRL_REG:
|
||||
// Handle PPUCTRL writes
|
||||
break;
|
||||
case PPUMASK_REG:
|
||||
// Handle PPUMASK writes
|
||||
break;
|
||||
case PPUSTATUS_REG:
|
||||
// Handle PPUSTATUS writes
|
||||
break;
|
||||
case OAMADDR_REG:
|
||||
// Handle OAMADDR writes
|
||||
break;
|
||||
case OAMDATA_REG:
|
||||
// Handle OAMDATA writes
|
||||
break;
|
||||
case PPUSCROLL_REG:
|
||||
// Handle PPUSCROLL writes
|
||||
break;
|
||||
case PPUADDR_REG:
|
||||
// Handle PPUADDR writes
|
||||
break;
|
||||
case PPUDATA_REG:
|
||||
// Handle PPUDATA writes
|
||||
break;
|
||||
default:
|
||||
// Other registers (shouldn't happen with mask)
|
||||
break;
|
||||
}}
|
||||
}}
|
||||
// APU and IO registers
|
||||
else if (address >= 0x4000 && address <= 0x4017) {{
|
||||
platform->memory[address] = value;
|
||||
|
||||
// Special handling for certain registers
|
||||
if (address == OAMDMA_REG) {{
|
||||
// Handle OAMDMA (would copy 256 bytes from CPU memory to OAM)
|
||||
uint16_t oam_src = value << 8; // High byte of address
|
||||
for (int i = 0; i < 256; i++) {{
|
||||
platform->memory[0x2004] = nes_platform_read(platform, oam_src + i);
|
||||
}}
|
||||
}}
|
||||
else if (address == JOY1 || address == JOY2) {{
|
||||
// Handle controller writes
|
||||
}}
|
||||
}}
|
||||
// Cartridge space
|
||||
else {{
|
||||
platform->memory[address] = value;
|
||||
}}
|
||||
}}
|
||||
|
||||
// Push a byte to the stack
|
||||
void nes_platform_push(NESPlatform* platform, uint8_t value) {{
|
||||
platform->memory[0x0100 + platform->sp] = value;
|
||||
platform->sp--;
|
||||
}}
|
||||
|
||||
// Pop a byte from the stack
|
||||
uint8_t nes_platform_pop(NESPlatform* platform) {{
|
||||
platform->sp++;
|
||||
return platform->memory[0x0100 + platform->sp];
|
||||
}}
|
||||
|
||||
void nes_platform_init(NESPlatform* platform) {{
|
||||
memset(platform, 0, sizeof(NESPlatform));
|
||||
|
||||
// Initialize CPU registers
|
||||
platform->a = 0;
|
||||
platform->x = 0;
|
||||
platform->y = 0;
|
||||
platform->sp = 0xFD; // Initial stack pointer
|
||||
platform->status = 0x34; // Initial status (I flag and U flag set)
|
||||
platform->pc = 0x8000; // Start of PRG ROM
|
||||
|
||||
// Initialize memory
|
||||
memset(platform->memory, 0, sizeof(platform->memory));
|
||||
memset(platform->ppu_memory, 0, sizeof(platform->ppu_memory));
|
||||
|
||||
// Enable debugging if requested
|
||||
platform->debug_enabled = debug_enabled;
|
||||
}}
|
||||
|
||||
void nes_platform_reset(NESPlatform* platform) {{
|
||||
if (platform->debug_enabled) {{
|
||||
printf(""Resetting NES platform...\n"");
|
||||
}}
|
||||
|
||||
// Read reset vector
|
||||
uint16_t reset_vector = platform->memory[0xFFFC] | (platform->memory[0xFFFD] << 8);
|
||||
|
||||
if (platform->debug_enabled) {{
|
||||
printf(""Reset vector: $%04X\n"", reset_vector);
|
||||
}}
|
||||
|
||||
// Set program counter to reset vector
|
||||
platform->pc = reset_vector;
|
||||
|
||||
// Set stack pointer
|
||||
platform->sp = 0xFD;
|
||||
|
||||
// Set status register (I flag and U flag set)
|
||||
platform->status = 0x34;
|
||||
}}
|
||||
|
||||
// Debug: Dump memory region
|
||||
void nes_platform_dump_memory(NESPlatform* platform, uint16_t start, uint16_t end) {{
|
||||
printf(""Memory dump from $%04X to $%04X:\n"", start, end);
|
||||
|
||||
for (uint16_t addr = start; addr <= end; addr++) {{
|
||||
if ((addr % 16) == 0) {{
|
||||
printf(""\n$%04X: "", addr);
|
||||
}}
|
||||
printf(""%02X "", nes_platform_read(platform, addr));
|
||||
}}
|
||||
printf(""\n"");
|
||||
}}
|
||||
|
||||
// Debug: Set breakpoint
|
||||
void nes_platform_set_breakpoint(NESPlatform* platform, uint16_t address, bool enabled) {{
|
||||
platform->breakpoints[address] = enabled;
|
||||
printf(""Breakpoint at $%04X %s\n"", address, enabled ? ""enabled"" : ""disabled"");
|
||||
}}
|
||||
|
||||
// Debug: Dump CPU state
|
||||
void nes_platform_dump_cpu_state(NESPlatform* platform) {{
|
||||
printf(""CPU State:\n"");
|
||||
printf("" PC: $%04X\n"", platform->pc);
|
||||
printf("" A: $%02X\n"", platform->a);
|
||||
printf("" X: $%02X\n"", platform->x);
|
||||
printf("" Y: $%02X\n"", platform->y);
|
||||
printf("" SP: $%02X\n"", platform->sp);
|
||||
printf("" Status: $%02X [%c%c%c%c%c%c%c%c]\n"",
|
||||
platform->status,
|
||||
(platform->status & NEGATIVE_FLAG) ? 'N' : '.',
|
||||
(platform->status & OVERFLOW_FLAG) ? 'V' : '.',
|
||||
(platform->status & UNUSED_FLAG) ? 'U' : '.',
|
||||
(platform->status & BREAK_FLAG) ? 'B' : '.',
|
||||
(platform->status & DECIMAL_FLAG) ? 'D' : '.',
|
||||
(platform->status & INTERRUPT_FLAG) ? 'I' : '.',
|
||||
(platform->status & ZERO_FLAG) ? 'Z' : '.',
|
||||
(platform->status & CARRY_FLAG) ? 'C' : '.');
|
||||
|
||||
uint8_t opcode = nes_platform_read(platform, platform->pc);
|
||||
printf("" Next: $%04X: $%02X ...\n"", platform->pc, opcode);
|
||||
}}
|
||||
|
||||
// Run the decompiled code using the platform abstraction
|
||||
void nes_platform_run_decompiled(NESPlatform* platform) {{
|
||||
printf(""==========================================================\n"");
|
||||
printf("" {solutionName} - NES Decompiled Code Runner \n"");
|
||||
printf(""==========================================================\n\n"");
|
||||
|
||||
// Print detected entry points
|
||||
printf(""Trying to identify entry points...\n"");
|
||||
|
||||
// Check if reset vector points to a valid location
|
||||
uint16_t reset_vector = platform->memory[0xFFFC] | (platform->memory[0xFFFD] << 8);
|
||||
printf(""Reset vector (from 0xFFFC): 0x%04X\n"", reset_vector);
|
||||
|
||||
// Try calling entry point functions if they exist
|
||||
bool entry_point_found = false;
|
||||
|
||||
// Define the most common entry points to try
|
||||
typedef void (*EntryFunc)();
|
||||
struct {{
|
||||
const char* name;
|
||||
EntryFunc func;
|
||||
bool exists;
|
||||
}} entry_points[] = {{
|
||||
#ifdef main
|
||||
{{ ""main"", (EntryFunc)main, true }},
|
||||
#else
|
||||
{{ ""main"", NULL, false }},
|
||||
#endif
|
||||
|
||||
#ifdef reset_handler
|
||||
{{ ""reset_handler"", reset_handler, true }},
|
||||
#else
|
||||
{{ ""reset_handler"", NULL, false }},
|
||||
#endif
|
||||
|
||||
#ifdef nmi_handler
|
||||
{{ ""nmi_handler"", nmi_handler, true }},
|
||||
#else
|
||||
{{ ""nmi_handler"", NULL, false }},
|
||||
#endif
|
||||
|
||||
// Common entry points based on memory location
|
||||
#ifdef sub_8000
|
||||
{{ ""sub_8000"", sub_8000, true }},
|
||||
#else
|
||||
{{ ""sub_8000"", NULL, false }},
|
||||
#endif
|
||||
|
||||
#ifdef sub_C000
|
||||
{{ ""sub_C000"", sub_C000, true }},
|
||||
#else
|
||||
{{ ""sub_C000"", NULL, false }},
|
||||
#endif
|
||||
|
||||
// Try to match the reset vector if defined as a function
|
||||
{{ ""reset_vector_func"", NULL, false }}
|
||||
}};
|
||||
|
||||
const int num_entry_points = sizeof(entry_points) / sizeof(entry_points[0]);
|
||||
|
||||
// Try each entry point
|
||||
for (int i = 0; i < num_entry_points; i++) {{
|
||||
// Special handling for reset vector
|
||||
if (strcmp(entry_points[i].name, ""reset_vector_func"") == 0) {{
|
||||
// Try to find a function that matches the reset vector
|
||||
char reset_func_name[32];
|
||||
sprintf(reset_func_name, ""sub_%04X"", reset_vector);
|
||||
printf("" Checking for function matching reset vector: %s"", reset_func_name);
|
||||
|
||||
printf("" (manual call required)\n"");
|
||||
continue;
|
||||
}}
|
||||
|
||||
// Check if this entry point exists
|
||||
if (entry_points[i].exists && entry_points[i].func != NULL) {{
|
||||
printf("" Found entry point: %s - Calling...\n"", entry_points[i].name);
|
||||
|
||||
// Call the entry point function
|
||||
entry_points[i].func();
|
||||
entry_point_found = true;
|
||||
|
||||
printf("" Finished executing %s\n"", entry_points[i].name);
|
||||
break;
|
||||
}}
|
||||
else {{
|
||||
printf("" Entry point not found: %s\n"", entry_points[i].name);
|
||||
}}
|
||||
}}
|
||||
|
||||
if (!entry_point_found) {{
|
||||
printf(""\n==========================================================\n"");
|
||||
printf(""WARNING: Could not find any valid entry point function!\n"");
|
||||
printf(""You need to manually call one of the decompiled functions.\n"");
|
||||
printf(""Common entry points may include:\n"");
|
||||
printf("" - sub_8000 (if ROM execution starts at 0x8000)\n"");
|
||||
printf("" - sub_C000 (if ROM execution starts at 0xC000)\n"");
|
||||
printf("" - A function corresponding to the reset vector address\n"");
|
||||
printf("" e.g., sub_%04X\n"", reset_vector);
|
||||
printf(""==========================================================\n"");
|
||||
}}
|
||||
|
||||
printf(""\nDecompiled code execution finished.\n"");
|
||||
}}
|
||||
|
||||
// Main function
|
||||
int main(int argc, char* argv[]) {{
|
||||
for (int i = 1; i < argc; i++) {{
|
||||
if (strcmp(argv[i], ""--debug"") == 0) {{
|
||||
debug_enabled = true;
|
||||
printf(""Debug mode enabled\n"");
|
||||
}}
|
||||
}}
|
||||
|
||||
nes_platform_init(&nes);
|
||||
|
||||
nes_platform_run_decompiled(&nes);
|
||||
|
||||
// If debugging, dump final state
|
||||
if (debug_enabled) {{
|
||||
nes_platform_dump_cpu_state(&nes);
|
||||
}}
|
||||
|
||||
return 0;
|
||||
}}
|
||||
";
|
||||
File.WriteAllText(Path.Combine(sourceDir, "main.c"), content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a CMakeLists.txt file for better project configuration
|
||||
/// </summary>
|
||||
private void CreateCMakeFile(string solutionDir)
|
||||
{
|
||||
string content = $@"cmake_minimum_required(VERSION 3.10)
|
||||
project({solutionName} C)
|
||||
|
||||
# Set C standard
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
# Add source files
|
||||
file(GLOB SOURCES src/*.c)
|
||||
file(GLOB HEADERS src/*.h)
|
||||
|
||||
# Create executable
|
||||
add_executable(${{PROJECT_NAME}} ${{SOURCES}} ${{HEADERS}})
|
||||
|
||||
# Add include directories
|
||||
target_include_directories(${{PROJECT_NAME}} PRIVATE src)
|
||||
|
||||
# Set warning level
|
||||
if(MSVC)
|
||||
target_compile_options(${{PROJECT_NAME}} PRIVATE /W4)
|
||||
else()
|
||||
target_compile_options(${{PROJECT_NAME}} PRIVATE -Wall -Wextra -pedantic)
|
||||
endif()
|
||||
|
||||
# Output binary to bin directory
|
||||
set_target_properties(${{PROJECT_NAME}} PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${{CMAKE_BINARY_DIR}}/bin
|
||||
)
|
||||
|
||||
# Install target
|
||||
install(TARGETS ${{PROJECT_NAME}} DESTINATION bin)
|
||||
";
|
||||
File.WriteAllText(Path.Combine(solutionDir, "CMakeLists.txt"), content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the Visual Studio solution file
|
||||
/// </summary>
|
||||
private void CreateSolutionFile(string solutionDir)
|
||||
{
|
||||
// Generate a unique GUID for the solution
|
||||
string solutionGuid = Guid.NewGuid().ToString("B").ToUpper();
|
||||
string projectGuid = Guid.NewGuid().ToString("B").ToUpper();
|
||||
|
||||
string content = $@"
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.32014.148
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project(""{projectGuid}"") = ""{solutionName}"", ""{solutionName}.vcxproj"", ""{Guid.NewGuid():B}""
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{{{Guid.NewGuid()}}}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{{{Guid.NewGuid()}}}.Debug|x64.Build.0 = Debug|x64
|
||||
{{{Guid.NewGuid()}}}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{{{Guid.NewGuid()}}}.Debug|x86.Build.0 = Debug|Win32
|
||||
{{{Guid.NewGuid()}}}.Release|x64.ActiveCfg = Release|x64
|
||||
{{{Guid.NewGuid()}}}.Release|x64.Build.0 = Release|x64
|
||||
{{{Guid.NewGuid()}}}.Release|x86.ActiveCfg = Release|Win32
|
||||
{{{Guid.NewGuid()}}}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {solutionGuid}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
";
|
||||
File.WriteAllText(Path.Combine(solutionDir, $"{solutionName}.sln"), content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the Visual Studio project file
|
||||
/// </summary>
|
||||
private void CreateProjectFile(string solutionDir)
|
||||
{
|
||||
string projectGuid = Guid.NewGuid().ToString().ToUpper();
|
||||
|
||||
string content = $@"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<Project DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
|
||||
<ItemGroup Label=""ProjectConfigurations"">
|
||||
<ProjectConfiguration Include=""Debug|Win32"">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include=""Release|Win32"">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include=""Debug|x64"">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include=""Release|x64"">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label=""Globals"">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{{{projectGuid}}}</ProjectGuid>
|
||||
<RootNamespace>{solutionName}</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project=""$(VCTargetsPath)\Microsoft.Cpp.Default.props"" />
|
||||
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Debug|Win32'"" Label=""Configuration"">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Release|Win32'"" Label=""Configuration"">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Debug|x64'"" Label=""Configuration"">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Release|x64'"" Label=""Configuration"">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project=""$(VCTargetsPath)\Microsoft.Cpp.props"" />
|
||||
<ImportGroup Label=""ExtensionSettings"">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label=""Shared"">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label=""PropertySheets"" Condition=""'$(Configuration)|$(Platform)'=='Debug|Win32'"">
|
||||
<Import Project=""$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"" Condition=""exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"" Label=""LocalAppDataPlatform"" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label=""PropertySheets"" Condition=""'$(Configuration)|$(Platform)'=='Release|Win32'"">
|
||||
<Import Project=""$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"" Condition=""exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"" Label=""LocalAppDataPlatform"" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label=""PropertySheets"" Condition=""'$(Configuration)|$(Platform)'=='Debug|x64'"">
|
||||
<Import Project=""$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"" Condition=""exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"" Label=""LocalAppDataPlatform"" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label=""PropertySheets"" Condition=""'$(Configuration)|$(Platform)'=='Release|x64'"">
|
||||
<Import Project=""$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"" Condition=""exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"" Label=""LocalAppDataPlatform"" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label=""UserMacros"" />
|
||||
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Debug|Win32'"">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Release|Win32'"">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Debug|x64'"">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Release|x64'"">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition=""'$(Configuration)|$(Platform)'=='Debug|Win32'"">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition=""'$(Configuration)|$(Platform)'=='Release|Win32'"">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition=""'$(Configuration)|$(Platform)'=='Debug|x64'"">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition=""'$(Configuration)|$(Platform)'=='Release|x64'"">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include=""src\main.c"" />
|
||||
<ClCompile Include=""src\{solutionName}.c"" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include=""src\nes_platform.h"" />
|
||||
<ClInclude Include=""src\{solutionName}.h"" />
|
||||
</ItemGroup>
|
||||
<Import Project=""$(VCTargetsPath)\Microsoft.Cpp.targets"" />
|
||||
<ImportGroup Label=""ExtensionTargets"">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
";
|
||||
File.WriteAllText(Path.Combine(solutionDir, $"{solutionName}.vcxproj"), content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the Visual Studio project filters file
|
||||
/// </summary>
|
||||
private void CreateFiltersFile(string solutionDir)
|
||||
{
|
||||
string content = $@"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<Project ToolsVersion=""4.0"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
|
||||
<ItemGroup>
|
||||
<Filter Include=""Source Files"">
|
||||
<UniqueIdentifier>{{{Guid.NewGuid()}}}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include=""Header Files"">
|
||||
<UniqueIdentifier>{{{Guid.NewGuid()}}}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include=""Resource Files"">
|
||||
<UniqueIdentifier>{{{Guid.NewGuid()}}}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include=""src\main.c"">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include=""src\{solutionName}.c"">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include=""src\nes_platform.h"">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include=""src\{solutionName}.h"">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
";
|
||||
File.WriteAllText(Path.Combine(solutionDir, $"{solutionName}.vcxproj.filters"), content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a README file with instructions
|
||||
/// </summary>
|
||||
private void CreateReadmeFile(string solutionDir)
|
||||
{
|
||||
string content = $@"# {solutionName} - Decompiled NES ROM
|
||||
|
||||
This project contains a decompiled version of a NES ROM, converted to C code that can be compiled and run on modern platforms.
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `src/{solutionName}.c` - The main decompiled code
|
||||
- `src/{solutionName}.h` - Header file with function and variable declarations
|
||||
- `src/nes_platform.h` - Platform abstraction layer for NES hardware
|
||||
- `src/main.c` - Entry point that sets up the environment and runs the decompiled code
|
||||
|
||||
## Building the Project
|
||||
|
||||
### Using Visual Studio 2022
|
||||
1. Open the solution file ({solutionName}.sln) in Visual Studio 2022
|
||||
2. Select the desired build configuration (Debug/Release) and platform (x86/x64)
|
||||
3. Build the solution (F7 or Build > Build Solution)
|
||||
4. Run the program (F5 or Debug > Start Debugging)
|
||||
|
||||
### Using CMake
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
## Running the Decompiled Code
|
||||
|
||||
The decompiled code is encapsulated in a wrapper that provides the necessary NES hardware emulation layer. This allows the code to run on modern systems without requiring a full NES emulator.
|
||||
|
||||
## Adapting for Other Platforms
|
||||
|
||||
To port this code to other platforms:
|
||||
|
||||
1. Modify the `nes_platform.h` file to match the target platform's capabilities
|
||||
2. Update memory access patterns in `main.c` if needed
|
||||
3. Replace platform-specific code with equivalents for the target platform
|
||||
|
||||
## Notes on Decompilation
|
||||
|
||||
The decompilation process attempts to recreate the original source code based on the binary ROM. Some aspects of the original code may be approximated or reconstructed in a way that produces equivalent behavior but doesn't match the original source exactly.
|
||||
|
||||
- Function boundaries may not exactly match the original source
|
||||
- Variable names are chosen by the decompiler and may not match original names
|
||||
- Control flow structures may be simplified or restructured
|
||||
- Code might not Compile directly and needs Modification, in this current state its more Pseudo-C than anything :p
|
||||
|
||||
## License
|
||||
|
||||
This decompiled code is provided for educational and research purposes only. All intellectual property rights to the original software remain with their respective owners.
|
||||
";
|
||||
File.WriteAllText(Path.Combine(solutionDir, "README.md"), content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the solution in Visual Studio if available
|
||||
/// </summary>
|
||||
/// <param name="solutionPath">The path to the solution file</param>
|
||||
public static void OpenInVisualStudio(string solutionPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(solutionPath))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = solutionPath,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Failed to open solution in Visual Studio: {ex.Message}",
|
||||
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.35931.197 d17.13
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NESDecompiler.Core", "NESDecompiler.Core\NESDecompiler.Core.csproj", "{BA91C551-5979-4498-A1C4-BEF79910D7F7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NESDecompiler.CLI", "NESDecompiler.CLI\NESDecompiler.CLI.csproj", "{38B4A069-4124-4206-BB38-6946ED9BF120}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NESDecompiler.GUI", "NESDecompiler.GUI\NESDecompiler.GUI.csproj", "{DE7EAAE5-189D-435E-8834-54FAE4DA8977}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BA91C551-5979-4498-A1C4-BEF79910D7F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BA91C551-5979-4498-A1C4-BEF79910D7F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BA91C551-5979-4498-A1C4-BEF79910D7F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BA91C551-5979-4498-A1C4-BEF79910D7F7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{38B4A069-4124-4206-BB38-6946ED9BF120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{38B4A069-4124-4206-BB38-6946ED9BF120}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{38B4A069-4124-4206-BB38-6946ED9BF120}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{38B4A069-4124-4206-BB38-6946ED9BF120}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DE7EAAE5-189D-435E-8834-54FAE4DA8977}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DE7EAAE5-189D-435E-8834-54FAE4DA8977}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DE7EAAE5-189D-435E-8834-54FAE4DA8977}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DE7EAAE5-189D-435E-8834-54FAE4DA8977}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {51005478-A548-469F-AEA4-CBB7818DFE04}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Reference in New Issue
Block a user