Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turn on nullable and remove as many warnings as possible #420

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ csharp_prefer_static_local_function = true:suggestion
csharp_prefer_simple_using_statement = false:none
csharp_style_prefer_switch_expression = true:suggestion
dotnet_style_readonly_field = true:suggestion
csharp_object_creation_when_type_evident = target_typed

# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
Expand Down
9 changes: 3 additions & 6 deletions .github/workflows/pr.yml → .github/workflows/merge.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
name: Serial Loops PR
run-name: PR for ${{ github.actor }} - ${{ github.ref_name }}
name: Serial Loops Merge
run-name: Validation for ${{ github.actor }} - ${{ github.ref_name }}
on:
pull_request:
types:
- opened
- reopened
push:
branches:
- 'main'
- 'Avalonia'
Expand Down
2 changes: 2 additions & 0 deletions SerialLoops.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeEditing/SuppressUninitializedWarningFix/Enabled/@EntryValue">False</s:Boolean></wpf:ResourceDictionary>
14 changes: 2 additions & 12 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,16 @@ jobs:
Windows:
imageName: 'windows-latest'
targetFramework: 'net8.0-windows'
displayName: Build & Test
displayName: Build Validation
pool:
vmImage: $(imageName)
steps:
- checkout: self
clean: true

- task: DotNetCoreCLI@2
inputs:
command: 'build'
projects: $(Build.SourcesDirectory)/src/SerialLoops/SerialLoops.csproj
arguments: -f $(targetFramework)
displayName: Build project

- task: DotNetCoreCLI@2
inputs:
command: 'test'
projects: $(Build.SourcesDirectory)/test/SerialLoops.Tests.Headless/SerialLoops.Tests.Headless.csproj
arguments: -f $(targetFramework)
publishTestResults: true
env:
ROM_URI: $(ChokuRomUri)
displayName: Run tests
24 changes: 12 additions & 12 deletions src/SerialLoops.Lib/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace SerialLoops.Lib;

public static class Build
{
public static bool BuildIterative(Project project, Config config, ILogger log, IProgressTracker tracker)
public static bool BuildIterative(Project project, Config? config, ILogger log, IProgressTracker tracker)
{
bool result = DoBuild(project.IterativeDirectory, project, config, log, tracker);
CopyToArchivesToIterativeOriginal(Path.Combine(project.IterativeDirectory, "rom", "data"),
Expand All @@ -32,7 +32,7 @@ public static bool BuildIterative(Project project, Config config, ILogger log, I
return result;
}

public static bool BuildBase(Project project, Config config, ILogger log, IProgressTracker tracker)
public static bool BuildBase(Project project, Config? config, ILogger log, IProgressTracker tracker)
{
bool result = DoBuild(project.BaseDirectory, project, config, log, tracker);
CopyToArchivesToIterativeOriginal(Path.Combine(project.BaseDirectory, "rom", "data"),
Expand Down Expand Up @@ -66,7 +66,7 @@ private static void CleanIterative(Project project, ILogger log, IProgressTracke
}
}

private static bool DoBuild(string directory, Project project, Config config, ILogger log, IProgressTracker tracker)
private static bool DoBuild(string directory, Project project, Config? config, ILogger log, IProgressTracker tracker)
{
// Export includes
StringBuilder commandsIncSb = new();
Expand Down Expand Up @@ -128,7 +128,7 @@ private static bool DoBuild(string directory, Project project, Config config, IL
}
else if (Path.GetExtension(file).Equals(".s", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(config.DevkitArmPath))
if (string.IsNullOrEmpty(config?.DevkitArmPath))
{
log.LogError("DevkitARM must be supplied in order to build!");
return false;
Expand Down Expand Up @@ -186,8 +186,8 @@ private static bool DoBuild(string directory, Project project, Config config, IL
tracker.Focus("Writing NitroPacker Project File", 1);
try
{
File.WriteAllBytes(ndsProjectFile, project.Settings.File.Write());
}
File.WriteAllBytes(ndsProjectFile, project.Settings!.File.Write());
}
catch (IOException exc)
{
log.LogException("Failed to write NitroPacker NDS project file to disk", exc);
Expand Down Expand Up @@ -238,7 +238,7 @@ private static void ReplaceSingleGraphicsFile(ArchiveFile<GraphicsFile> grp, str
grpFile.InitializeFontFile();
}

GraphicInfo graphicInfo = JsonSerializer.Deserialize<GraphicInfo>(File.ReadAllText(Path.Combine(Path.GetDirectoryName(filePath), $"{Path.GetFileNameWithoutExtension(filePath)}.gi")));
GraphicInfo graphicInfo = JsonSerializer.Deserialize<GraphicInfo>(File.ReadAllText(Path.Combine(Path.GetDirectoryName(filePath)!, $"{Path.GetFileNameWithoutExtension(filePath)}.gi")))!;

graphicInfo.Set(grpFile);
grpFile.SetImage(filePath, newSize: true);
Expand Down Expand Up @@ -315,8 +315,8 @@ private static (string, string) CompileSourceFile(string filePath, string devkit
{
string exeExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty;

string objFile = $"{Path.Combine(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath))}.o";
string binFile = $"{Path.Combine(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath))}.bin";
string objFile = $"{Path.Combine(Path.GetDirectoryName(filePath)!, Path.GetFileNameWithoutExtension(filePath))}.o";
string binFile = $"{Path.Combine(Path.GetDirectoryName(filePath)!, Path.GetFileNameWithoutExtension(filePath))}.bin";
ProcessStartInfo gccStartInfo = new(Path.Combine(devkitArm, "bin", $"arm-none-eabi-gcc{exeExtension}"), $"-c -nostdlib -static \"{filePath}\" -o \"{objFile}")
{
CreateNoWindow = true,
Expand All @@ -331,8 +331,8 @@ private static (string, string) CompileSourceFile(string filePath, string devkit
}
log.Log($"Compiling '{filePath}' to '{objFile}' with '{gccStartInfo.FileName}'...");
Process gcc = new() { StartInfo = gccStartInfo };
gcc.OutputDataReceived += (object sender, DataReceivedEventArgs e) => log.Log(e.Data);
gcc.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => log.LogWarning(e.Data);
gcc.OutputDataReceived += (_, e) => log.Log(e.Data);
gcc.ErrorDataReceived += (_, e) => log.LogWarning(e.Data);
gcc.Start();
gcc.WaitForExit();
Task.Delay(50); // ensures process is actually complete
Expand Down Expand Up @@ -417,4 +417,4 @@ private static void ReplaceSingleBinaryFile(ArchiveFile<GraphicsFile> archive, s
log.LogException(string.Format(localize("Failed replacing animation file {0} in grp.bin with file '{1}'"), index, filePath), ex);
}
}
}
}
48 changes: 22 additions & 26 deletions src/SerialLoops.Lib/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ namespace SerialLoops.Lib;
public class Config
{
[JsonIgnore]
public string ConfigPath { get; set; }
public string UserDirectory { get; set; }
public string ConfigPath { get; set; } = string.Empty;
public string UserDirectory { get; set; } = string.Empty;
[JsonIgnore]
public string ProjectsDirectory => Path.Combine(UserDirectory, "Projects");
[JsonIgnore]
Expand All @@ -28,22 +28,24 @@ public class Config
public string HacksDirectory => Path.Combine(UserDirectory, "Hacks");
[JsonIgnore]
public string ScriptTemplatesDirectory => Path.Combine(UserDirectory, "ScriptTemplates");

[JsonIgnore]
public ObservableCollection<AsmHack> Hacks { get; set; }
public ObservableCollection<AsmHack> Hacks { get; set; } = [];

[JsonIgnore]
public ObservableCollection<ScriptTemplate> ScriptTemplates { get; set; }
public string CurrentCultureName { get; set; }
public string DevkitArmPath { get; set; }
public ObservableCollection<ScriptTemplate> ScriptTemplates { get; set; } = [];
public string CurrentCultureName { get; set; } = string.Empty;
public string DevkitArmPath { get; set; } = string.Empty;
public bool UseDocker { get; set; }
public string DevkitArmDockerTag { get; set; }
public string EmulatorFlatpak { get; set; }
public string EmulatorPath { get; set; }
public string DevkitArmDockerTag { get; set; } = string.Empty;
public string EmulatorFlatpak { get; set; } = string.Empty;
public string EmulatorPath { get; set; } = string.Empty;
public bool AutoReopenLastProject { get; set; }
public bool RememberProjectWorkspace { get; set; }
public bool RemoveMissingProjects { get; set; }
public bool CheckForUpdates { get; set; }
public bool PreReleaseChannel { get; set; }
public string DisplayFont { get; set; }
public string DisplayFont { get; set; } = string.Empty;

public void Save(ILogger log)
{
Expand All @@ -56,14 +58,8 @@ public void ValidateConfig(Func<string, string> localize, ILogger log)
{
log.LogError(localize("devkitARM is not detected at the default or specified install location. Please set devkitARM path."));
}
if (CurrentCultureName is null)
{
CurrentCultureName = CultureInfo.CurrentCulture.Name;
}
else
{
CultureInfo.CurrentCulture = new(CurrentCultureName);
}

CultureInfo.CurrentCulture = new(CurrentCultureName);
}

public void InitializeHacks(ILogger log)
Expand All @@ -75,20 +71,20 @@ public void InitializeHacks(ILogger log)
File.Copy(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "hacks.json"), Path.Combine(HacksDirectory, "hacks.json"));
}

Hacks = JsonSerializer.Deserialize<ObservableCollection<AsmHack>>(File.ReadAllText(Path.Combine(HacksDirectory, "hacks.json")));
Hacks = JsonSerializer.Deserialize<ObservableCollection<AsmHack>>(File.ReadAllText(Path.Combine(HacksDirectory, "hacks.json"))) ?? [];

// Pull in new hacks in case we've updated the program with more
List<AsmHack> builtinHacks = JsonSerializer.Deserialize<List<AsmHack>>(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "hacks.json")));
IEnumerable<AsmHack> missingHacks = builtinHacks.Where(h => !Hacks.Contains(h));
if (missingHacks.Any())
List<AsmHack> builtinHacks = JsonSerializer.Deserialize<List<AsmHack>>(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "hacks.json"))) ?? [];
AsmHack[] missingHacks = [.. builtinHacks.Where(h => !Hacks.Contains(h))];
if (missingHacks.Length != 0)
{
IO.CopyFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Hacks"), HacksDirectory, log);
Hacks.AddRange(missingHacks);
File.WriteAllText(Path.Combine(HacksDirectory, "hacks.json"), JsonSerializer.Serialize(Hacks));
}

IEnumerable<AsmHack> updatedHacks = builtinHacks.Where(h => !Hacks.FirstOrDefault(o => h.Name == o.Name)?.DeepEquals(h) ?? false);
if (updatedHacks.Any())
AsmHack[] updatedHacks = [.. builtinHacks.Where(h => !Hacks.FirstOrDefault(o => h.Name == o.Name)?.DeepEquals(h) ?? false)];
if (updatedHacks.Length != 0)
{
IO.CopyFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Hacks"), HacksDirectory, log);
foreach (AsmHack updatedHack in updatedHacks)
Expand Down Expand Up @@ -123,7 +119,7 @@ internal void InitializeScriptTemplates(Func<string, string> localize, ILogger l
{
try
{
templates.Add(JsonSerializer.Deserialize<ScriptTemplate>(File.ReadAllText(scriptTemplateFile)));
templates.Add(JsonSerializer.Deserialize<ScriptTemplate>(File.ReadAllText(scriptTemplateFile))!);
}
catch (Exception ex)
{
Expand All @@ -132,4 +128,4 @@ internal void InitializeScriptTemplates(Func<string, string> localize, ILogger l
}
ScriptTemplates = new(templates);
}
}
}
22 changes: 16 additions & 6 deletions src/SerialLoops.Lib/Factories/ConfigFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace SerialLoops.Lib.Factories;
// "You've forgotten the mocks."
public class ConfigFactory : IConfigFactory
{
public Config LoadConfig(Func<string, string> localize, ILogger log)
public Config? LoadConfig(Func<string, string> localize, ILogger log)
{
string configJson = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SerialLoops", "config.json");

Expand All @@ -39,7 +39,12 @@ public Config LoadConfig(Func<string, string> localize, ILogger log)

try
{
Config config = JsonSerializer.Deserialize<Config>(File.ReadAllText(configJson));
Config? config = JsonSerializer.Deserialize<Config>(File.ReadAllText(configJson));
if (config is null)
{
log.LogError(localize("Failed to parse config.json; null value returned!"));
return HandleDefaultConfig(configJson, localize, log);
}
config.ValidateConfig(localize, log);
config.ConfigPath = configJson;
config.InitializeHacks(log);
Expand All @@ -49,10 +54,7 @@ public Config LoadConfig(Func<string, string> localize, ILogger log)
catch (JsonException exc)
{
log.LogException(localize("Exception occurred while parsing config.json!"), exc);
Config defaultConfig = GetDefault(log);
defaultConfig.ValidateConfig(localize, log);
IO.WriteStringFile(configJson, JsonSerializer.Serialize(defaultConfig), log);
return defaultConfig;
return HandleDefaultConfig(configJson, localize, log);
}
}

Expand Down Expand Up @@ -141,4 +143,12 @@ public static Config GetDefault(ILogger log)
PreReleaseChannel = false,
};
}

private Config? HandleDefaultConfig(string configJson, Func<string, string> localize, ILogger log)
{
Config? defaultConfig = GetDefault(log);
defaultConfig.ValidateConfig(localize, log);
IO.WriteStringFile(configJson, JsonSerializer.Serialize(defaultConfig), log);
return defaultConfig;
}
}
2 changes: 1 addition & 1 deletion src/SerialLoops.Lib/Factories/IConfigFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ namespace SerialLoops.Lib.Factories;

public interface IConfigFactory
{
public Config LoadConfig(Func<string, string> localize, ILogger log);
public Config? LoadConfig(Func<string, string> localize, ILogger log);
}
31 changes: 18 additions & 13 deletions src/SerialLoops.Lib/Flags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,56 +11,61 @@ namespace SerialLoops.Lib;
public class Flags
{
public const int NUM_FLAGS = 5120;
public static readonly Dictionary<int, string> _names = JsonSerializer.Deserialize<Dictionary<int, string>>(File.ReadAllText(Extensions.GetLocalizedFilePath(Path.Combine("Defaults", "DefaultFlags"), "json")));
public static readonly Dictionary<int, string>? _names = JsonSerializer.Deserialize<Dictionary<int, string>>(File.ReadAllText(Extensions.GetLocalizedFilePath(Path.Combine("Defaults", "DefaultFlags"), "json")));

public static string GetFlagNickname(int flag, Project project)
{
if (_names.TryGetValue(flag, out string value))
if (_names is null)
{
return string.Empty;
}

if (_names.TryGetValue(flag, out string? value))
{
return value;
}

TopicItem topic = (TopicItem)project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Topic && ((TopicItem)i).TopicEntry.Id == flag);
TopicItem? topic = (TopicItem?)project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Topic && ((TopicItem)i).TopicEntry?.Id == flag);
if (topic is not null)
{
return string.Format(project.Localize("{0} Obtained"), topic.DisplayName);
}

TopicItem readTopic = (TopicItem)project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Topic &&
((((TopicItem)i).TopicEntry.Type == TopicType.Main && ((TopicItem)i).HiddenMainTopic is not null && ((TopicItem)i).TopicEntry.Id + 3463 == flag) ||
(((TopicItem)i).TopicEntry.Type != TopicType.Main && ((TopicItem)i).TopicEntry.Id + 3451 == flag)));
TopicItem? readTopic = (TopicItem?)project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Topic &&
((((TopicItem)i).TopicEntry?.Type == TopicType.Main && ((TopicItem)i).HiddenMainTopic is not null && ((TopicItem)i).TopicEntry?.Id + 3463 == flag) ||
(((TopicItem)i).TopicEntry?.Type != TopicType.Main && ((TopicItem)i).TopicEntry?.Id + 3451 == flag)));
if (readTopic is not null)
{
return string.Format(project.Localize("{0} Watched in Extras"), readTopic.DisplayName);
}

BackgroundMusicItem bgm = (BackgroundMusicItem)project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.BGM && ((BackgroundMusicItem)i).Flag == flag);
BackgroundMusicItem? bgm = (BackgroundMusicItem?)project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.BGM && ((BackgroundMusicItem)i).Flag == flag);
if (bgm is not null)
{
return string.Format(project.Localize("Listened to {0}"), bgm.DisplayName);
}

BackgroundItem bg = (BackgroundItem)project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Background && ((BackgroundItem)i).Flag == flag);
BackgroundItem? bg = (BackgroundItem?)project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Background && ((BackgroundItem)i).Flag == flag);
if (bg is not null && flag > 0)
{
return string.Format(project.Localize("{0} ({1}) Seen"), bg.CgName, bg.DisplayName);
}

GroupSelectionItem groupSelection = (GroupSelectionItem)project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Group_Selection &&
GroupSelectionItem? groupSelection = (GroupSelectionItem?)project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Group_Selection &&
((GroupSelectionItem)i).Selection.Activities.Any(a => a?.Routes.Any(r => r?.Flag == flag) ?? false));
if (groupSelection is not null)
{
ScenarioRoute route = groupSelection.Selection.Activities.First(a => a?.Routes.Any(r => r.Flag == flag) ?? false).Routes.First(r => r.Flag == flag);
return string.Format(project.Localize("Route \"{0}\" Completed"), route.Title.GetSubstitutedString(project));
}

ScriptItem script = (ScriptItem)project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Script &&
flag >= ((ScriptItem)i).StartReadFlag && flag < ((ScriptItem)i).StartReadFlag + ((ScriptItem)i).Event.ScriptSections.Count);
ScriptItem? script = (ScriptItem?)project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Script &&
flag >= ((ScriptItem)i).StartReadFlag && flag < ((ScriptItem)i).StartReadFlag + ((ScriptItem)i).Event!.ScriptSections.Count);
if (script is not null)
{
return string.Format(project.Localize("Script {0} Section {1} Completed"), script.DisplayName, script.Event.ScriptSections[flag - script.StartReadFlag].Name);
return string.Format(project.Localize("Script {0} Section {1} Completed"), script.DisplayName, script.Event?.ScriptSections[flag - script.StartReadFlag].Name);
}

return $"F{flag:D2}";
}
}
}
Loading