diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml index d57388a..52ad60e 100644 --- a/.github/workflows/dotnet-desktop.yml +++ b/.github/workflows/dotnet-desktop.yml @@ -34,10 +34,8 @@ jobs: with: repository: PlatinumMaster/NitroSharp path: NitroSharp - - run: dotnet restore SwissArmyKnife - - run: dotnet build SwissArmyKnife --configuration Release --no-restore + - run: dotnet build SwissArmyKnife --configuration Release - run: dotnet publish -c Release SwissArmyKnife - - name: Upload build artifacts (Windows) if: ${{ matrix.os == 'windows-latest' }} uses: actions/upload-artifact@v2 @@ -68,10 +66,10 @@ jobs: filename: SAK-${{matrix.os}}-${{ env.TAG }}.zip - name: Make release uses: ncipollo/release-action@v1 + if: github.ref == 'refs/heads/master' with: allowUpdates: true - prerelease: true tag: ${{ env.TAG }} commit: ${{ github.sha }} - name: Nightly ${{ github.sha }} + name: Release ${{ github.sha }} artifacts: SAK-${{matrix.os}}-${{ env.TAG }}.zip diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 083a15d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: csharp -mono: none -dotnet: 5.0.302 -solution: SwissArmyKnife.sln -install: - - sudo dosomething - - nuget restore solution-name.sln \ No newline at end of file diff --git a/App.axaml b/App.axaml index 057b5b2..936b369 100644 --- a/App.axaml +++ b/App.axaml @@ -1,12 +1,17 @@ + xmlns:local="using:SwissArmyKnife" + xmlns:themes="clr-namespace:Material.Styles.Themes;assembly=Material.Styles" + xmlns:avalonia="clr-namespace:SwissArmyKnife" + x:Class="SwissArmyKnife.App"> + + + - - - - - - + + + + + - \ No newline at end of file + diff --git a/App.axaml.cs b/App.axaml.cs index 09dadeb..c5a60dd 100644 --- a/App.axaml.cs +++ b/App.axaml.cs @@ -1,19 +1,28 @@ -using Avalonia; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Markup.Xaml; -using SwissArmyKnife.Avalonia.Views; - -namespace SwissArmyKnife.Avalonia { - public class App : Application { - public override void Initialize() { - AvaloniaXamlLoader.Load(this); - } - - public override void OnFrameworkInitializationCompleted() { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - desktop.MainWindow = new ProjectManagementWindow(); - - base.OnFrameworkInitializationCompleted(); - } - } +using System.IO; +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using SwissArmyKnife.Handlers; +using SwissArmyKnife.ViewModels.Main; +using SwissArmyKnife.Views; + +namespace SwissArmyKnife { + public partial class App : Application { + public override void Initialize() { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() { + Logging.InitializeLogger(); + Net.Initialize(); + Preferences.ReadPreferences(Path.Combine("Configuration", "Preferences.yml")); + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { + desktop.MainWindow = new StartupWindow { + DataContext = new StartupViewModel() + }; + } + + base.OnFrameworkInitializationCompleted(); + } + } } \ No newline at end of file diff --git a/Assets/BattleCategory.png b/Assets/BattleCategory.png new file mode 100644 index 0000000..688c8d0 Binary files /dev/null and b/Assets/BattleCategory.png differ diff --git a/Assets/MapEditor.png b/Assets/MapEditor.png index 7e3f5ac..060cac6 100644 Binary files a/Assets/MapEditor.png and b/Assets/MapEditor.png differ diff --git a/Assets/sak-icon.ico b/Assets/ProgramIcon.ico similarity index 100% rename from Assets/sak-icon.ico rename to Assets/ProgramIcon.ico diff --git a/Assets/ScriptEditor.png b/Assets/ScriptEditor.png index 35a94ac..51945ef 100644 Binary files a/Assets/ScriptEditor.png and b/Assets/ScriptEditor.png differ diff --git a/Assets/TextEditor.png b/Assets/TextEditor.png index 86d8ccb..2468ef5 100644 Binary files a/Assets/TextEditor.png and b/Assets/TextEditor.png differ diff --git a/Assets/TrainerEditor.png b/Assets/TrainerEditor.png new file mode 100644 index 0000000..8b58f82 Binary files /dev/null and b/Assets/TrainerEditor.png differ diff --git a/Assets/WildEditor.png b/Assets/WildEditor.png new file mode 100644 index 0000000..b02b5a3 Binary files /dev/null and b/Assets/WildEditor.png differ diff --git a/Assets/ZoneEditor.png b/Assets/ZoneEditor.png new file mode 100644 index 0000000..a288b1b Binary files /dev/null and b/Assets/ZoneEditor.png differ diff --git a/Assets/ZoneEntitiesEditor.png b/Assets/ZoneEntitiesEditor.png index 25274a6..7544c27 100644 Binary files a/Assets/ZoneEntitiesEditor.png and b/Assets/ZoneEntitiesEditor.png differ diff --git a/Assets/avalonia-logo.ico b/Assets/avalonia-logo.ico deleted file mode 100644 index da8d49f..0000000 Binary files a/Assets/avalonia-logo.ico and /dev/null differ diff --git a/Assets/icon.png b/Assets/icon.png deleted file mode 100644 index a03bbac..0000000 Binary files a/Assets/icon.png and /dev/null differ diff --git a/Converters/MapMatrixRowConverter.cs b/Converters/MapMatrixRowConverter.cs deleted file mode 100644 index 8556a65..0000000 --- a/Converters/MapMatrixRowConverter.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Data; -using System.Globalization; -using Avalonia.Data.Converters; -using BeaterLibrary.Formats.Maps; - -namespace SwissArmyKnife.Avalonia.Converters; - -public class MapMatrixRowConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - var matrix = value as ObservableCollection; - if (matrix != null) { - int nRows, nColumns; - if ((nRows = matrix.Count) > 0 && (nColumns = matrix[0].Count) > 0) { - DataTable t = new DataTable(); - for (int index = 0; index < nColumns; ++index) { - t.Columns.Add(new DataColumn(index.ToString())); - } - for (int row = 0; row < nRows; ++row) { - DataRow newRow = t.NewRow(); - for (int column = 0; column < nColumns; ++column) { - newRow[column] = matrix[row][column]; - } - t.Rows.Add(newRow); - } - - return t.DefaultView; - } - } - return null; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - return null; - } -} \ No newline at end of file diff --git a/Handlers/CommandUpdateHandler.cs b/Handlers/CommandUpdateHandler.cs deleted file mode 100644 index f067a43..0000000 --- a/Handlers/CommandUpdateHandler.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.IO; -using System.Net; -using SwissArmyKnife.Avalonia.Utils; - -namespace SwissArmyKnife.Avalonia.Handlers { - public static class CommandUpdateHandler { - public static bool FetchScriptCommands() { - using (var w = new WebClient()) { - if (!DownloadYml(w, "Base", Path.Combine("Resources", "Scripts", UI.GameInfo.title, "Base.yml"))) - throw new WebException("Failed to download script commands"); - foreach (var plugin in UI.GameInfo.scriptPlugins) - if (!DownloadYml(w, $"Overlay {plugin}", - Path.Combine("Resources", "Scripts", UI.GameInfo.title, $"Overlay {plugin}.yml"))) - throw new WebException($"Failed to download script plugin {plugin}."); - - return true; - } - } - - public static bool DownloadYml(WebClient w, string ymlName, string expPath) { - DirectoryInfo reqPath = Directory.GetParent(expPath); - if (!reqPath.Exists) { - reqPath.Create(); - } - w.DownloadFile(new Uri(GetLinkToYml(ymlName)), expPath); - return true; - } - - public static string GetLinkToYml(string ymlName) { - return $"{PreferencesHandler.Prefs.ScriptCommandsLink}{UI.GameInfo.title}/{ymlName}.yml"; - } - } -} \ No newline at end of file diff --git a/Handlers/Commands.cs b/Handlers/Commands.cs new file mode 100644 index 0000000..4f825e8 --- /dev/null +++ b/Handlers/Commands.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace SwissArmyKnife.Handlers { + public class Commands { + public static async Task Fetch() { + string ScriptResBase = Path.Combine("Resources", "Scripts", GameWork.Project.GameInfo.Codename); + string NetWkPath = Path.Combine("Resources", "NetWk"); + + DirectoryInfo BaseDir = new DirectoryInfo(ScriptResBase), NetWkDir = new DirectoryInfo(NetWkPath); + if (!BaseDir.Exists) { + BaseDir.Create(); + } + + if (!NetWkDir.Exists) { + NetWkDir.Create(); + } + + if (!await Net.DownloadFile(GetLinkToYML("Base.yml"), Path.Combine(NetWkPath, "Base.yml"))) { + NetWkDir.Delete(true); + return false; + } + + foreach (KeyValuePair PluginMap in GameWork.Project.GameInfo.ScriptPlugins) { + string OVLYML = $"Overlay {PluginMap.Key}.yml"; + if (!await Net.DownloadFile(GetLinkToYML(OVLYML), Path.Combine(NetWkPath, OVLYML))) { + NetWkDir.Delete(true); + return false; + } + } + + foreach (FileInfo File in NetWkDir.GetFiles()) { + File.CopyTo(Path.Combine(ScriptResBase, $"{File.Name}"), true); + } + + NetWkDir.Delete(true); + return true; + } + + public static string GetLinkToYML(string ymlName) => $"{Preferences.Prefs.ScriptCommandsLink}{GameWork.Project.GameInfo.Codename}/{ymlName}"; + } +} \ No newline at end of file diff --git a/Handlers/GameWork.cs b/Handlers/GameWork.cs new file mode 100644 index 0000000..62bd829 --- /dev/null +++ b/Handlers/GameWork.cs @@ -0,0 +1,18 @@ +using Hotswap; +using Hotswap.Project; + +namespace SwissArmyKnife.Handlers; + +public class GameWork { + public static Patcher? Patcher { get; private set; } + public static IProject? Project { get; private set; } + public static void Init(string BaseROMConfigurationPath) { + Patcher = new Patcher(BaseROMConfigurationPath); + } + + public static bool HandleProject(IProject Configuration) { + Project = Configuration; + Patcher?.LoadProject(Configuration); + return true; + } +} \ No newline at end of file diff --git a/Handlers/IO.cs b/Handlers/IO.cs new file mode 100644 index 0000000..a8f030c --- /dev/null +++ b/Handlers/IO.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Avalonia.Controls; + +namespace SwissArmyKnife.Handlers; + +public class IO { + private static async Task HandleFolderFileChoice(bool saving, bool isFile, Window parentInstance, List dialogFilter) { + string? result; + if (isFile) { + if (saving) { + result = await new SaveFileDialog { + Filters = dialogFilter + }.ShowAsync(parentInstance); + } + else { + string[]? resultArray = await new OpenFileDialog { + Filters = dialogFilter + }.ShowAsync(parentInstance); + result = resultArray != null && resultArray.Length != 0 ? resultArray[0] : null; + } + } else { + result = await new OpenFolderDialog().ShowAsync(parentInstance); + } + + return string.IsNullOrEmpty(result) ? String.Empty : result; + } + + public static async Task OpenFolder(Window parentInstance) => await HandleFolderFileChoice(false, false, parentInstance, null); + public static async Task OpenFile(Window parentInstance, List dialogFilter) => await HandleFolderFileChoice(false, true, parentInstance, dialogFilter); + public static async Task SaveFile(Window parentInstance, List dialogFilter) => await HandleFolderFileChoice(true, true, parentInstance, dialogFilter); + + public static async Task TryReadBinaryFile(Window Instance, List Filters) { + string? FilePath = await OpenFile(Instance, Filters); + if (!FilePath.Equals("")) { + return await File.ReadAllBytesAsync(FilePath); + } + return null; + } + + public static async Task TryWriteBinaryFile(Window Instance, byte[] Buffer, List Filters) { + string? FilePath = await SaveFile(Instance, Filters); + if (!FilePath.Equals("")) { + await File.WriteAllBytesAsync(FilePath, Buffer); + } + } +} \ No newline at end of file diff --git a/Handlers/Logging.cs b/Handlers/Logging.cs new file mode 100644 index 0000000..da5741c --- /dev/null +++ b/Handlers/Logging.cs @@ -0,0 +1,40 @@ +using System; +using System.IO; +using Avalonia.Controls; + +namespace SwissArmyKnife.Handlers; + +public static class Logging { + private static TextWriter loggerStream; + private static string logFileName; + + private static void Log(string Message) { + if (!Design.IsDesignMode) { + using (loggerStream = File.AppendText(logFileName)) { + loggerStream.WriteLine($"[{DateTime.Now}] {Message}"); + loggerStream.Flush(); + loggerStream.Close(); + } + } + } + + public static void LogWarning(string Message) => Log($": {Message}"); + public static void LogError(string Message) => Log($": {Message}"); + public static void LogStandard(string Message) => Log($"{Message}"); + + public static void StopLogger() => loggerStream.Close(); + + public static void InitializeLogger() { + if (!Design.IsDesignMode) { + string baseName = $"SAKLog-{DateTime.Today.Year}{DateTime.Today.Month}{DateTime.Today.Day}"; + int sessionNumber = 0; + + Directory.CreateDirectory("Logs"); + while (File.Exists(logFileName = Path.Combine("Logs", $"{baseName}-{sessionNumber}.log"))) { + sessionNumber++; + } + Log($"# SwissArmyKnife - (Commit Version Here) - Log File"); + } + } + +} \ No newline at end of file diff --git a/Handlers/MessageHandler.cs b/Handlers/MessageHandler.cs deleted file mode 100644 index 2e11d41..0000000 --- a/Handlers/MessageHandler.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Threading.Tasks; -using MessageBox.Avalonia; -using MessageBox.Avalonia.DTO; -using MessageBox.Avalonia.Enums; - -namespace SwissArmyKnife.Avalonia.Handlers { - internal class MessageHandler { - public static void ErrorMessage(string title, string caption) { - MessageBoxManager.GetMessageBoxStandardWindow(new MessageBoxStandardParams { - ButtonDefinitions = ButtonEnum.Ok, - ContentTitle = $"Error: {title}", - ContentMessage = caption, - Icon = Icon.Error, - Style = Style.None - }).Show(); - } - - public static Task YesNoMessage(string title, string caption) { - return MessageBoxManager.GetMessageBoxStandardWindow(new MessageBoxStandardParams { - ButtonDefinitions = ButtonEnum.YesNo, - ContentTitle = title, - ContentMessage = caption, - Icon = Icon.Success, - Style = Style.Windows - }).Show(); - } - } -} \ No newline at end of file diff --git a/Handlers/Messages.cs b/Handlers/Messages.cs new file mode 100644 index 0000000..7625730 --- /dev/null +++ b/Handlers/Messages.cs @@ -0,0 +1,47 @@ +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Material.Dialog; +using Material.Icons; +using Material.Icons.Avalonia; + +namespace SwissArmyKnife.Handlers; + +public class Messages { + private static async Task DoMessage(IClassicDesktopStyleApplicationLifetime Desktop, string Header, string SupportingText, DialogButton[] Buttons, MaterialIconExt MsgIcon) { + Logging.LogStandard($"MessageBox (Icon {nameof(MsgIcon)} spawned, with title \"${Header}\" and content \"{SupportingText}\""); + return await DialogHelper.CreateAlertDialog( + new AlertDialogBuilderParams { + ContentHeader = Header, + SupportingText = SupportingText, + StartupLocation = WindowStartupLocation.CenterOwner, + NegativeResult = new DialogResult("No"), + Borderless = true, + DialogButtons = Buttons, + }).ShowDialog(Desktop.MainWindow); + } + public static Task YesNo(IClassicDesktopStyleApplicationLifetime Desktop, string Header, string SupportingText) => DoMessage(Desktop, Header, SupportingText, new[] { + new DialogButton { + Content = "Yes", + Result = "Confirm" + }, + new DialogButton { + Content = "No", + Result = "Cancel" + } + }, new MaterialIconExt(MaterialIconKind.Warning)); + + public static Task Generic(IClassicDesktopStyleApplicationLifetime Desktop, string Header, string SupportingText) => DoMessage(Desktop, Header, SupportingText, new[] { + new DialogButton { + Content = "Okay", + Result = "Confirm" + }, + }, new MaterialIconExt(MaterialIconKind.Information)); + + public static Task Error(IClassicDesktopStyleApplicationLifetime Desktop, string Header, string SupportingText) => DoMessage(Desktop, Header, SupportingText, new[] { + new DialogButton { + Content = "Okay", + Result = "Confirm" + }, + }, new MaterialIconExt(MaterialIconKind.Error)); +} \ No newline at end of file diff --git a/Handlers/Net.cs b/Handlers/Net.cs new file mode 100644 index 0000000..baed3fb --- /dev/null +++ b/Handlers/Net.cs @@ -0,0 +1,27 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; + +namespace SwissArmyKnife.Handlers { + public class Net { + private static HttpClient Client; + + public static void Initialize () { + Client = new HttpClient(new SocketsHttpHandler { + PooledConnectionLifetime = TimeSpan.FromSeconds(30) + }); + } + + public static async Task DownloadFile(string URL, string SavePath) { + HttpResponseMessage Message = await Client.GetAsync(URL); + if (Message.IsSuccessStatusCode) { + using (FileStream fs = new FileStream(SavePath, FileMode.OpenOrCreate)) { + await Message.Content.CopyToAsync(fs); + } + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/Handlers/Preferences.cs b/Handlers/Preferences.cs new file mode 100644 index 0000000..ed5a2a2 --- /dev/null +++ b/Handlers/Preferences.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.IO; +using YamlDotNet.Serialization; + +namespace SwissArmyKnife.Handlers; + +public class Preferences { + public static Models.Preferences Prefs; + public static void ReadPreferences(string path) { + Dictionary Preferences = new Dictionary { + { "ScriptCommandsLink", "https://raw.githubusercontent.com/HelloOO7/PokeScriptSDK5/master/yml/" }, + { "BaseROMConfigurationPath", Path.Combine("Configuration", "BaseROM.yml") } + }; + + Dictionary ScriptEditorColors = new Dictionary { + { "Method", "Cyan" }, + { "Comment", "Green" }, + { "Operator", "Fuchsia" }, + { "Parameter", "Orange" } + }; + + Dictionary TextEditorColors = new Dictionary { + { "Command", "" }, + { "Comment", "" }, + { "Operator", "" }, + { "Parameter", "" } + }; + + if (!File.Exists(path)) { + Logging.LogWarning($"Preferences file not found at path \"{path}\"; using default configuration."); + } else { + Logging.LogStandard($"Loading preferences from {path}..."); + Dictionary DeserializedPreferences = + new Deserializer().Deserialize>(new StringReader(File.ReadAllText(path))); + if (DeserializedPreferences.ContainsKey("ScriptCommandsRepository")) { + var NewScriptRepository = DeserializedPreferences["ScriptCommandsRepository"] as string; + if (NewScriptRepository != null) { + Preferences["ScriptCommandsLink"] = NewScriptRepository; + Logging.LogStandard($"Changed script repository to {NewScriptRepository}."); + } + else { + Logging.LogWarning($"Invalid script repository link. Using the default."); + } + } + + if (DeserializedPreferences.ContainsKey("BaseROMConfigurationPath")) { + var NewBaseRomConfigurationPath = DeserializedPreferences["BaseROMConfigurationPath"] as string; + if (NewBaseRomConfigurationPath != null) { + Preferences["BaseROMConfigurationPath"] = NewBaseRomConfigurationPath; + Logging.LogStandard($"Changed BaseROM configuration path to {NewBaseRomConfigurationPath}."); + } + else { + Logging.LogWarning($"Invalid BaseROM configuration path. Using the default."); + } + } + + if (DeserializedPreferences.ContainsKey("ScriptEditorColors")) { + Dictionary? NewScriptEditorColors = DeserializedPreferences["ScriptEditorColors"] as Dictionary; + if (NewScriptEditorColors != null) { + foreach (KeyValuePair KVP in NewScriptEditorColors) + if (ScriptEditorColors.ContainsKey(KVP.Key)) { + ScriptEditorColors[KVP.Key] = KVP.Value; + Logging.LogStandard($"Changed the script editor's {KVP.Key} color to {KVP.Value}."); + } + } + else { + Logging.LogWarning($"Invalid script editor color configuration. Using the default."); + } + } + + if (DeserializedPreferences.ContainsKey("TextEditorColors")) { + Dictionary? NewTextEditorColors = + DeserializedPreferences["TextEditorColors"] as Dictionary; + if (NewTextEditorColors != null) { + foreach (KeyValuePair KVP in NewTextEditorColors) + if (TextEditorColors.ContainsKey(KVP.Key)) { + TextEditorColors[KVP.Key] = KVP.Value; + Logging.LogStandard($"Changed the text editor's {KVP.Key} color to {KVP.Value}."); + } + } + else { + Logging.LogWarning($"Invalid text editor color configuration. Using the default."); + } + } + } + + Prefs = new Models.Preferences { + ScriptCommandsLink = Preferences["ScriptCommandsLink"], + BaseROMConfigurationPath = Preferences["BaseROMConfigurationPath"], + ScriptEditorColors = ScriptEditorColors, + TextEditorColors = TextEditorColors + }; + + Logging.LogStandard($"Done loading preferences."); + } + + public static void Serialize(string path) { } +} \ No newline at end of file diff --git a/Handlers/PreferencesHandler.cs b/Handlers/PreferencesHandler.cs deleted file mode 100644 index 38d5f1c..0000000 --- a/Handlers/PreferencesHandler.cs +++ /dev/null @@ -1,13 +0,0 @@ -using SwissArmyKnife.Avalonia.Models; - -namespace SwissArmyKnife.Avalonia.Handlers { - public class PreferencesHandler { - public static Preferences Prefs = new(); - - public static void ReadPreferences() { - } - - public static void Serialize(Preferences prefs, string path) { - } - } -} \ No newline at end of file diff --git a/Handlers/Scripts.cs b/Handlers/Scripts.cs new file mode 100644 index 0000000..0988670 --- /dev/null +++ b/Handlers/Scripts.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Avalonia.Markup.Xaml.Templates; +using BeaterLibrary; +using BeaterLibrary.Formats.Scripts; + +namespace SwissArmyKnife.Handlers; + +public class Scripts { + private static List ParsedJumpOffsets = new List(); + public static string PrintMethod(ScriptMethod Method, int ScriptIndex = -1, bool IsAnonymous = false) { + StringBuilder ConvertedMethod = new StringBuilder(); + ConvertedMethod.AppendLine(IsAnonymous ? $"AnonymousScriptMethod_{Method.Address}:" + : $"Script_{ScriptIndex}:"); + List JumpAddresses = Method.Commands.FindAll(Commands => Commands.Type.Equals(Commands.Type == CommandTypes.Jump)).Select(Command => (int)Command.Parameters[^1]).ToList(); + int BaseAddress = Method.Address; + foreach (Command Cmd in Method.Commands) { + if (JumpAddresses.Contains(BaseAddress) && !ParsedJumpOffsets.Contains(BaseAddress)) { + ConvertedMethod.AppendLine($"AnonymousScriptMethod_{BaseAddress}:"); + ParsedJumpOffsets.Append(BaseAddress); + } else { + ConvertedMethod.AppendLine($"\t{Cmd}"); + } + BaseAddress += Cmd.Size(); + } + return ConvertedMethod.ToString(); + } + + public static void Reset() { + ParsedJumpOffsets.Clear(); + } + + public static void Assemble(string ExportPath, string Script, int Plugin) { + // Generate include. + StringBuilder s = new StringBuilder(); + Util.GenerateIncludes(GameWork.Project.GameInfo.Abbreviation, "Resources/Scripts", Plugin); + + // Generate headers. + s.AppendLine($".include \"{GameWork.Project.GameInfo.Abbreviation}.s\""); + s.AppendLine($"Header:"); + Regex r = new Regex(@".*Script_\w+:.*"); + foreach (Match m in r.Matches(Script)) { + s.AppendLine($"\tscript {m.Value.Split(':')[0]}"); + } + s.AppendLine("EndHeader"); + using (StreamWriter sw = new StreamWriter("Temp.s")) { + sw.Write(string.Join(Environment.NewLine, s.ToString(), Script)); + } + + Assembler("Temp.s", "Temp.o"); + ObjectCopy("Temp.o", ExportPath); + } + + private static void Subprocess(string program, string args) { + var proc = new Process(); + proc.StartInfo = new ProcessStartInfo { + FileName = program, + Arguments = args, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + proc.Start(); + proc.WaitForExit(); + + var errorOutput = proc.StandardError.ReadToEnd(); + + if (proc.ExitCode != 0) { + throw new Exception(errorOutput); + } + } + + public static void Assembler(string path, string output) { + Subprocess("arm-none-eabi-as", $"-mthumb -c \"{path}\" -o \"{output}\""); + } + + public static void ObjectCopy(string path, string output) { + Subprocess("arm-none-eabi-objcopy", $"-O binary \"{path}\" \"{output}\""); + } +} \ No newline at end of file diff --git a/Models/Editors/MapEditorModel.cs b/Models/Editors/MapEditorModel.cs new file mode 100644 index 0000000..89fd529 --- /dev/null +++ b/Models/Editors/MapEditorModel.cs @@ -0,0 +1,83 @@ +using System; +using BeaterLibrary.Formats.Maps; +using BeaterLibrary.Formats.Nitro; +using ReactiveUI; + +namespace SwissArmyKnife.Models.Editors; + +public class MapEditorModel : ReactiveObject { + private MapContainer _Container; + public MapContainer Container { + get => _Container; + set { + this.RaiseAndSetIfChanged(ref _Container, value); + UpdateContainerType(); + this.RaisePropertyChanged(nameof(ModelName)); + this.RaisePropertyChanged(nameof(ContainerType)); + this.RaisePropertyChanged(nameof(MapModel)); + this.RaisePropertyChanged(nameof(Permissions)); + this.RaisePropertyChanged(nameof(Permissions2)); + this.RaisePropertyChanged(nameof(BuildingPositions)); + } + } + + public ushort ContainerType { + get => (ushort) Array.IndexOf(MapContainer.MapContainerType.GetValues(Container.ContainerType.GetType()), Container.ContainerType); + } + + public string ModelName { + get => Container.Model.Name; + } + + public NSBMD MapModel { + get => Container.Model; + set { + Container.Model = value; + } + } + + public byte[] Permissions { + get => Container.Permissions; + set { + Container.Permissions = value; + UpdateContainerType(); + } + } + + public bool HasPermissions => Permissions != null && Permissions.Length > 0; + + public byte[] Permissions2 { + get => Container.Permissions2; + set { + Container.Permissions2 = value; + UpdateContainerType(); + } + } + + public bool HasPermissions2 => Permissions2 != null && Permissions2.Length > 0; + + public byte[] BuildingPositions { + get => Container.BuildingPositions; + set { + Container.BuildingPositions = value; + } + } + + public bool HasBuildingPositions => BuildingPositions != null && BuildingPositions.Length > 0; + + public void UpdateContainerType() { + // Assume model & building positions are set. + if (Permissions != null && Permissions2 == null) { + // WB or RD chunk. My tool does not support RD natively. + Container.ContainerType = MapContainer.MapContainerType.WB; + } else if (Permissions != null && Permissions2 != null) { + // GC Chunk + Container.ContainerType = MapContainer.MapContainerType.GC; + } else { + // NG Chunk + Container.ContainerType = MapContainer.MapContainerType.NG; + } + } + + public int Index { get; set; } +} \ No newline at end of file diff --git a/Models/Editors/MatrixEditorModel.cs b/Models/Editors/MatrixEditorModel.cs new file mode 100644 index 0000000..bcb542e --- /dev/null +++ b/Models/Editors/MatrixEditorModel.cs @@ -0,0 +1,10 @@ +using System.Data; +using ReactiveUI; + +namespace SwissArmyKnife.Models.Editors; + +public class MatrixEditorModel : ReactiveObject { + public int Index { get; set; } + public DataView MapFiles { get; set; } + public DataView MapHeaders { get; set; } +} \ No newline at end of file diff --git a/Models/Editors/ZoneEntitiesModel.cs b/Models/Editors/ZoneEntitiesModel.cs new file mode 100644 index 0000000..15acd8b --- /dev/null +++ b/Models/Editors/ZoneEntitiesModel.cs @@ -0,0 +1,13 @@ +using BeaterLibrary.Formats.Zone; +using ReactiveUI; + +namespace SwissArmyKnife.Models.Editors; + +public class ZoneEntitiesModel : ReactiveObject { + private ZoneEntities _Container; + public ZoneEntities Container { + get => _Container; + set => this.RaiseAndSetIfChanged(ref _Container, value); + } + public int Index { get; set; } +} \ No newline at end of file diff --git a/Models/NewProjectModel.cs b/Models/NewProjectModel.cs new file mode 100644 index 0000000..8630b41 --- /dev/null +++ b/Models/NewProjectModel.cs @@ -0,0 +1,28 @@ +using System; +using ReactiveUI; + +namespace SwissArmyKnife.Models; + +public class NewProjectModel : ReactiveObject { + private string _projectName, _projectTitle, _projectROMCode; + + public NewProjectModel() { + ProjectName = String.Empty; + ProjectROMCode = String.Empty; + Path = String.Empty; + } + public string ProjectName { + get => _projectName; + set => this.RaiseAndSetIfChanged(ref _projectName, value); + } + + public string Path { + get => _projectTitle; + set => this.RaiseAndSetIfChanged(ref _projectTitle, value); + } + + public string ProjectROMCode { + get => _projectROMCode; + set => this.RaiseAndSetIfChanged(ref _projectROMCode, value); + } +} \ No newline at end of file diff --git a/Models/Preferences.cs b/Models/Preferences.cs index bcf61ea..b508c1e 100644 --- a/Models/Preferences.cs +++ b/Models/Preferences.cs @@ -1,11 +1,15 @@ -namespace SwissArmyKnife.Avalonia.Models { - public class Preferences { - public Preferences() { - ScriptCommandsLink = "https://raw.githubusercontent.com/HelloOO7/PokeScriptSDK5/master/yml/"; - BaseRomConfiguration = "Configuration/BaseROM.yml"; - } +using System.Collections.Generic; - public string ScriptCommandsLink { get; } - public string BaseRomConfiguration { get; } +namespace SwissArmyKnife.Models; + +public class Preferences { + public Preferences() { + ScriptCommandsLink = "https://raw.githubusercontent.com/PlatinumMaster/PokeScriptSDK5/master/yml/"; + BaseROMConfigurationPath = "Configuration/BaseROM.yml"; } + + public string ScriptCommandsLink { get; set; } + public string BaseROMConfigurationPath { get; set; } + public Dictionary ScriptEditorColors { get; set; } + public Dictionary TextEditorColors { get; set; } } \ No newline at end of file diff --git a/Pages/Battle/TrainerEditor.axaml b/Pages/Battle/TrainerEditor.axaml new file mode 100644 index 0000000..2f9bddd --- /dev/null +++ b/Pages/Battle/TrainerEditor.axaml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Pages/Battle/TrainerEditor.axaml.cs b/Pages/Battle/TrainerEditor.axaml.cs new file mode 100644 index 0000000..4c06d6b --- /dev/null +++ b/Pages/Battle/TrainerEditor.axaml.cs @@ -0,0 +1,16 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using SwissArmyKnife.ViewModels.Editors; + +namespace SwissArmyKnife.Pages.Battle; + +public partial class TrainerEditor : UserControl { + public TrainerEditor() { + DataContext = new TrainerEditorViewModel(); + InitializeComponent(); + } + + private void InitializeComponent() { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/Pages/Battle/WildPokemonEditor.axaml b/Pages/Battle/WildPokemonEditor.axaml new file mode 100644 index 0000000..76e043d --- /dev/null +++ b/Pages/Battle/WildPokemonEditor.axaml @@ -0,0 +1,1812 @@ + + + + + + + + + +