SM64-TAS-ABC / STROOP

SuperMario64 Technical Run-time Observer and Object Processor
170 stars 22 forks source link

Build STROOP on Linux platforms (with Mono and Docker) #72

Open norbjd opened 4 years ago

norbjd commented 4 years ago

Hello :hand:,

I'd like to help on this project but I am not working on a Windows platform. I'm not an expert in C# too. I'm just a regular developer impressed by the work made on this project to find new strategies for the different challenges (ABC and others).

So, I'm trying to build the application under Linux environment using Mono, an open-source implementation of Microsoft's .NET Framework. I'm using a Docker image to create reproducible builds, using an official base Mono image based on Debian 9 (Stretch).

You can see the different updates I've made here on my fork of STROOP and the branch (linux-build) I've created : https://github.com/SM64-TAS-ABC/STROOP/compare/Development...norbjd:6f219e3c6efa509a045504f4163db9c8c43ceb70.

To summarize quickly, here are the steps I have made :

The difference between STROOP/STROOP.csproj and STROOP/STROOP_linux.csproj can be seen using diff tool :

437,461c437,461
<     <Compile Include="Ttc\TtcPendulum2.cs" />
<     <Compile Include="Ttc\TtcRng2.cs" />
<     <Compile Include="Ttc\TtcSaveStateByteIterator.cs" />
<     <Compile Include="Ttc\TtcUtilities.cs" />
<     <Compile Include="Ttc\TtcSimulation.cs" />
<     <Compile Include="Ttc\TtcRng.cs" />
<     <Compile Include="Ttc\TtcMain.cs" />
<     <Compile Include="Ttc\TtcSaveState.cs" />
<     <Compile Include="Ttc\TtcWheel.cs" />
<     <Compile Include="Ttc\TtcTreadmill.cs" />
<     <Compile Include="Ttc\TtcThwomp.cs" />
<     <Compile Include="Ttc\TtcSpinningTriangle.cs" />
<     <Compile Include="Ttc\TtcSpinner.cs" />
<     <Compile Include="Ttc\TtcRotatingTriangularPrism.cs" />
<     <Compile Include="Ttc\TtcPusher.cs" />
<     <Compile Include="Ttc\TtcRotatingBlock.cs" />
<     <Compile Include="Ttc\TtcPitBlock.cs" />
<     <Compile Include="Ttc\TtcPendulum.cs" />
<     <Compile Include="Ttc\TtcHand.cs" />
<     <Compile Include="Ttc\TtcElevator.cs" />
<     <Compile Include="Ttc\TtcDust.cs" />
<     <Compile Include="Ttc\TtcCog.cs" />
<     <Compile Include="Ttc\TtcBobomb.cs" />
<     <Compile Include="Ttc\TtcAmp.cs" />
<     <Compile Include="Ttc\TtcObject.cs" />
---
>     <Compile Include="TTC\TTCPendulum2.cs" />
>     <Compile Include="TTC\TTCRng2.cs" />
>     <Compile Include="TTC\TTCSaveStateByteIterator.cs" />
>     <Compile Include="TTC\TTCUtilities.cs" />
>     <Compile Include="TTC\TTCSimulation.cs" />
>     <Compile Include="TTC\TTCRng.cs" />
>     <Compile Include="TTC\TTCMain.cs" />
>     <Compile Include="TTC\TTCSaveState.cs" />
>     <Compile Include="TTC\TTCWheel.cs" />
>     <Compile Include="TTC\TTCTreadmill.cs" />
>     <Compile Include="TTC\TTCThwomp.cs" />
>     <Compile Include="TTC\TTCSpinningTriangle.cs" />
>     <Compile Include="TTC\TTCSpinner.cs" />
>     <Compile Include="TTC\TTCRotatingTriangularPrism.cs" />
>     <Compile Include="TTC\TTCPusher.cs" />
>     <Compile Include="TTC\TTCRotatingBlock.cs" />
>     <Compile Include="TTC\TTCPitBlock.cs" />
>     <Compile Include="TTC\TTCPendulum.cs" />
>     <Compile Include="TTC\TTCHand.cs" />
>     <Compile Include="TTC\TTCElevator.cs" />
>     <Compile Include="TTC\TTCDust.cs" />
>     <Compile Include="TTC\TTCCog.cs" />
>     <Compile Include="TTC\TTCBobomb.cs" />
>     <Compile Include="TTC\TTCAmp.cs" />
>     <Compile Include="TTC\TTCObject.cs" />
1026,1029c1026,1029
<     <PostBuildEvent>del "$(TargetDir)OpenTK.dll.config"
< del "$(TargetDir)OpenTK.GLControl.xml"
< del "$(TargetDir)OpenTK.xml"
< del "$(TargetDir)STROOP.exe.config"
---
>     <PostBuildEvent>rm -f "$(TargetDir)OpenTK.dll.config"
> rm -f "$(TargetDir)OpenTK.GLControl.xml"
> rm -f "$(TargetDir)OpenTK.xml"
> rm -f "$(TargetDir)STROOP.exe.config"
1033c1033
<     <PreBuildEvent>xcopy /E /Y /I "$(ProjectDir)Resources" "$(TargetDir)Resources" /D</PreBuildEvent>
---
>     <PreBuildEvent>cp -R "$(ProjectDir)Resources" "$(TargetDir)Resources"</PreBuildEvent>

When building, I've got some errors :

"/app/STROOP.sln" (default target) (1) ->
"/app/STROOP/STROOP.csproj" (default target) (2) ->
(CoreCompile target) -> 
  Controls/VisualLinkLineText.cs(10,28): error CS0234: The type or namespace name 'TextFormatting' does not exist in the namespace 'System.Windows.Media' (are you missing an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/DecompilerView.xaml.cs(11,22): error CS0234: The type or namespace name 'Controls' does not exist in the namespace 'System.Windows' (are you missing an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/DecompilerView.xaml.cs(13,22): error CS0234: The type or namespace name 'Documents' does not exist in the namespace 'System.Windows' (are you missing an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/DecompilerView.xaml.cs(16,28): error CS0234: The type or namespace name 'Imaging' does not exist in the namespace 'System.Windows.Media' (are you missing an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/DecompilerView.xaml.cs(17,22): error CS0234: The type or namespace name 'Navigation' does not exist in the namespace 'System.Windows' (are you missing an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/DecompilerView.xaml.cs(18,22): error CS0234: The type or namespace name 'Shapes' does not exist in the namespace 'System.Windows' (are you missing an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/VisualLinkLineText.cs(43,25): error CS0246: The type or namespace name 'TextRun' could not be found (are you missing a using directive or an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/VisualLinkLineText.cs(59,47): error CS0246: The type or namespace name 'QueryCursorEventArgs' could not be found (are you missing a using directive or an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/VisualLinkLineText.cs(68,45): error CS0246: The type or namespace name 'MouseButtonEventArgs' could not be found (are you missing a using directive or an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/VisualLinkLineText.cs(43,33): error CS0012: The type 'TextRun' is defined in an assembly that is not referenced. You must add a reference to assembly 'PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. [/app/STROOP/STROOP.csproj]
  Controls/DecompilerView.xaml.cs(26,43): error CS0246: The type or namespace name 'UserControl' could not be found (are you missing a using directive or an assembly reference?) [/app/STROOP/STROOP.csproj]

I have fixed some of them by removing some unused (maybe?) using imports, more precisely in STROOP/Controls/DecompilerView.xaml.cs (see this commit : https://github.com/norbjd/STROOP/commit/c217ea5134e939f22f5cffa01eb7350e25b04b45). This may be wrong (it may add some side effects?), but it removed some errors :

"/app/STROOP.sln" (default target) (1) ->
"/app/STROOP/STROOP.csproj" (default target) (2) ->
(CoreCompile target) -> 
  Controls/VisualLinkLineText.cs(10,28): error CS0234: The type or namespace name 'TextFormatting' does not exist in the namespace 'System.Windows.Media' (are you missing an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/DecompilerView.xaml.cs(21,43): error CS0246: The type or namespace name 'UserControl' could not be found (are you missing a using directive or an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/VisualLinkLineText.cs(43,25): error CS0246: The type or namespace name 'TextRun' could not be found (are you missing a using directive or an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/VisualLinkLineText.cs(59,47): error CS0246: The type or namespace name 'QueryCursorEventArgs' could not be found (are you missing a using directive or an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/VisualLinkLineText.cs(68,45): error CS0246: The type or namespace name 'MouseButtonEventArgs' could not be found (are you missing a using directive or an assembly reference?) [/app/STROOP/STROOP.csproj]
  Controls/VisualLinkLineText.cs(43,33): error CS0012: The type 'TextRun' is defined in an assembly that is not referenced. You must add a reference to assembly 'PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. [/app/STROOP/STROOP.csproj]

I have also noticed that some warnings appeared before the errors (which may explain the errors) :

"/app/STROOP.sln" (default target) (1) ->
"/app/STROOP/STROOP.csproj" (default target) (2) ->
(ResolveAssemblyReferences target) -> 
  /usr/lib/mono/msbuild/Current/bin/Microsoft.Common.CurrentVersion.targets(2101,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "PresentationCore". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [/app/STROOP/STROOP.csproj]
  /usr/lib/mono/msbuild/Current/bin/Microsoft.Common.CurrentVersion.targets(2101,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "PresentationFramework". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [/app/STROOP/STROOP.csproj]
  /usr/lib/mono/msbuild/Current/bin/Microsoft.Common.CurrentVersion.targets(2101,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "UIAutomationProvider". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [/app/STROOP/STROOP.csproj]
  /usr/lib/mono/msbuild/Current/bin/Microsoft.Common.CurrentVersion.targets(2101,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "WindowsFormsIntegration". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [/app/STROOP/STROOP.csproj]

The build could not locate the assemblies PresentationCore, PresentationFramework, UIAutomationProvider and WindowsFormsIntegration. Indeed, WPF (Windows Presentation Foundation) APIs are not implemented by Mono. That explains why PresentationCore is not available for example.

By not using WPF APIs, it will be possible to build STROOP for Linux (and probably MacOS). It may (or not) attract some developers like me to contribute to this project. The problem is : I don't know how many efforts one have to make to not use WPF APIs. @scob will probably be able to answer this question. Do you think it's worth it? Maybe I'm chasing ghosts here.

If you have any questions, feel free to ask.

Thank you very much and great job on this tool once again. :+1:

onlymx13 commented 4 years ago

From what I’ve heard, STROOP builds fine on wine. I don’t think Pannen has any plans to make it build natively.

norbjd commented 4 years ago

@onlymx13 Thanks for your answer.

It will run probably fine on Wine, but this was just a question I had looking at the project. Using Mono, a developer using a GNU/Linux distribution would have directly all the tools to develop/debug the application in an IDE (example : VS Code with Mono debug plugin) without using Wine every time to build the software and not be able to debug effectively.

In fact, this is also more a question of curiosity, to understand what is the effort to refactor STROOP to not use the WPF APIs (I mean, if there is for example only ten files to change, then it won't probably be too difficult to migrate - I can help on this :smiley: - but I may be wrong). Using Mono also opens the door for using technologies like Docker to create reproducible builds and images. And it may attract also GNU Linux/MacOS developers for contributing.

norbjd commented 4 years ago

I made further researches, and it looks like hopefully Mono supports WinForms. The 4 missing assemblies used by WPF and missing are the following, as the last warning message suggests in my first message :

I figured out that each one is bound to a particular namespace :

The 2 last namespaces are not used in the entire STROOP project.

Imports of System.Windows and System.Windows.Media are used in only 4 files (7 matches) :

$ echo; grep -Ern 'using System.Windows;|using System.Windows.Media' . | sort

./STROOP/Controls/DecompilerView.xaml.cs:10:using System.Windows;
./STROOP/Controls/DecompilerView.xaml.cs:14:using System.Windows.Media;
./STROOP/Controls/VisualLinkLineText.cs:10:using System.Windows.Media.TextFormatting;
./STROOP/Controls/VisualLinkLineText.cs:7:using System.Windows;
./STROOP/Controls/VisualLinkLineText.cs:9:using System.Windows.Media;
./STROOP/Utilities/MoreMath.cs:9:using System.Windows;
./STROOP/Utilities/SubtitleUtilities.cs:11:using System.Windows;

Concerned C# files are :

I believe (although not sure) that without these imports, STROOP could be build natively on Linux with Mono.

I'll try to see what I can do.

norbjd commented 4 years ago

I removed Controls\DecompilerView.xaml.cs and Controls\VisualLinkLineText.cs, and their dependencies (Controls\DecompilerView.xaml and Controls\VisualRegexLinkGenerator.cs) from the .csproj build file. These files are not used widely in STROOP, so this is fine.

But I got new build errors now (more) :

"/app/STROOP.sln" (default target) (1) ->
"/app/STROOP/STROOP.csproj" (default target) (2) ->
(CoreCompile target) -> 
  Controls/WatchVariableControl.cs(335,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(336,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(337,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(338,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(339,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(340,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(341,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(342,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(343,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(344,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(345,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(346,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(347,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(348,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(349,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(351,35): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(352,28): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(353,32): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(354,31): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(746,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(747,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/WatchVariableControl.cs(748,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(18,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(19,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(20,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(21,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(22,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(23,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(24,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(25,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(26,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(27,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(38,20): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(38,56): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(43,20): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(43,57): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(48,20): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(48,55): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(53,20): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(54,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/KeyboardUtilities.cs(55,17): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/ObjectSlot.cs(568,84): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/ObjectSlot.cs(570,88): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/ObjectSlot.cs(573,90): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/ObjectSlot.cs(576,82): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Controls/ObjectSlot.cs(587,71): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Utilities/SubtitleUtilities.cs(19,36): error CS0103: The name 'Clipboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Managers/MemoryManager.cs(389,21): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Managers/MemoryManager.cs(390,21): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Managers/MemoryManager.cs(391,21): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]
  Managers/MemoryManager.cs(392,21): error CS0103: The name 'Keyboard' does not exist in the current context [/app/STROOP/STROOP.csproj]

There are 2 distinct error messages :

Keyboard class is indeed present in PresentationCore.dll , like Clipboard class.

So, my last message was wrong, my mistake (not a C# expert) : DLL hides many namespaces. PresentationFramework.dll hides other namespaces, for example System.Windows.Documents. Same for PresentationCore.dll :

There are probably other namespaces hidden behind these DLLs, but having no Windows system, I can't find what's behind all these 4 missing DLLs (maybe a kind Windows user could "decompile" them to help me find all namespaces behind :pray:).

Unfortunately, System.Windows.Input (across Keyboard class mainly) is widely used across STROOP :

$ echo; grep -Ern 'using System.Windows.Input' . | sort

./STROOP/Controls/DecompilerView.xaml.cs:13:using System.Windows.Input;
./STROOP/Controls/ObjectSlot.cs:17:using System.Windows.Input;
./STROOP/Controls/VisualLinkLineText.cs:8:using System.Windows.Input;
./STROOP/Controls/WatchVariableControl.cs:11:using System.Windows.Input;
./STROOP/Managers/CellsManager.cs:13:using System.Windows.Input;
./STROOP/Managers/CoinManager.cs:13:using System.Windows.Input;
./STROOP/Managers/MemoryManager.cs:13:using System.Windows.Input;
./STROOP/Managers/ObjectSlotsManager.cs:15:using System.Windows.Input;
./STROOP/Managers/SearchManager.cs:14:using System.Windows.Input;
./STROOP/Utilities/DictionaryUtilities.cs:10:using System.Windows.Input;
./STROOP/Utilities/KeyboardUtilities.cs:10:using System.Windows.Input;

There are many dependencies to these files (except Controls/DecompilerView.xaml.cs and Controls/VisualLinkLineText.cs, already removed from the build), so, sadly :disappointed:, I don't think I'll be able to fix these errors.

norbjd commented 4 years ago

I was able to make some progress to make STROOP Mono-compatible (thus, natively GNU/Linux compatible).

As for the Keyboard references, I've moved them to Utilities/KeyboardUtilities.cs to centralize all keyboard interactions within a single class. I've changed all the references to Keyboard.[...] to KeyboardUtilities.[...] (no big changes). But, as for now, the methods are not implemented, I just wanted to know if the application could be launched with Mono first.

There was also an error with Clipboard in Utilities/SubtitleUtilities, so I removed the reference. I am aware that this needs to be fixed, but I repeat here, my goal was just to see if STROOP could be built with Mono.

At this step, after fixing the errors, I was finally able to build STROOP with Mono in a Docker container by running :

docker build . -t 'stroop'

I then tried to run STROOP :

# enable app to be displayed on the host
xhost +local:stroop

# run the image
docker run \
    --name stroop \
    --hostname stroop \
    --rm -i -t  \
    -e DISPLAY \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    -w /app/STROOP/bin/Debug/ \
    stroop \
    mono STROOP.exe

# remove X display permission on the host
xhost -local:stroop

Basically here, STROOP is running in a container but the GUI is run on the host by sharing the X11 socket (/tmp/.X11-unix) and exporting DISPLAY. This is the simplest way to run GUI apps with Docker AFAIK.

But there was an issue with some resources. Indeed, the separator used for directories in Config/*.xml files was \ (for Windows). But, for GNU/Linux, this separator is /. When running STROOP, consequences were that resources under directories could not be found. Rather than replacing in *.xml files, I've simply replaced programmatically in Utilities/XmlConfigParser.cs, only when needed, the \ character to Path.DirectorySeparatorChar (on GNU/Linux, this is /, and on Windows, there is no change), like this :

--- a/STROOP/Utilities/XmlConfigParser.cs
+++ b/STROOP/Utilities/XmlConfigParser.cs
@@ -255,16 +255,16 @@ namespace STROOP.Utilities
                             switch(subElement.Name.ToString())
                             {
                                 case "ImageDirectory":
-                                    imageDir = subElement.Value;
+                                    imageDir = subElement.Value.Replace('\\', Path.DirectorySeparatorChar);
                                     break;
                                 case "DefaultImage":
                                     defaultImagePath = subElement.Value;
                                     break;
                                 case "MapImageDirectory":
-                                    mapImageDir = subElement.Value;
+                                    mapImageDir = subElement.Value.Replace('\\', Path.DirectorySeparatorChar);
                                     break;
                                 case "OverlayImageDirectory":
-                                    overlayImageDir = subElement.Value;
+                                    overlayImageDir = subElement.Value.Replace('\\', Path.DirectorySeparatorChar);
                                     break;
                                 case "EmptyImage":
                                     emptyImagePath = subElement.Value;
@@ -697,15 +697,15 @@ namespace STROOP.Utilities
                             {
                                 case "ClassicInputImageDirectory":
                                     guiList.Add(CreateInputImageAssoc(
-                                        path, subElement.Value, InputDisplayTypeEnum.Classic));
+                                        path, subElement.Value.Replace('\\', Path.DirectorySeparatorChar), InputDisplayTypeEnum.Classic));
                                     break;
                                 case "SleekInputImageDirectory":
                                     guiList.Add(CreateInputImageAssoc(
-                                        path, subElement.Value, InputDisplayTypeEnum.Sleek));
+                                        path, subElement.Value.Replace('\\', Path.DirectorySeparatorChar), InputDisplayTypeEnum.Sleek));
                                     break;
                                 case "VerticalInputImageDirectory":
                                     guiList.Add(CreateInputImageAssoc(
-                                        path, subElement.Value, InputDisplayTypeEnum.Vertical));
+                                        path, subElement.Value.Replace('\\', Path.DirectorySeparatorChar), InputDisplayTypeEnum.Vertical));
                                     break;
                             }
                         }
@@ -934,7 +934,7 @@ namespace STROOP.Utilities
                             switch (subElement.Name.ToString())
                             {
                                 case "FileImageDirectory":
-                                    fileImageDir = subElement.Value;
+                                    fileImageDir = subElement.Value.Replace('\\', Path.DirectorySeparatorChar);
                                     break;
                             }
                         }
@@ -1177,7 +1177,7 @@ namespace STROOP.Utilities
                                     assoc.MapImageFolderPath = subElement.Value;
                                     break;
                                 case "BackgroundImageDirectory":
-                                    assoc.BackgroundImageFolderPath = subElement.Value;
+                                    assoc.BackgroundImageFolderPath = subElement.Value.Replace('\\', Path.DirectorySeparatorChar);
                                     break;
                                 case "DefaultImage":
                                     var defaultMap = new MapLayout() { ImagePath = subElement.Value };
@@ -1290,7 +1290,7 @@ namespace STROOP.Utilities
                             switch (subElement.Name.ToString())
                             {
                                 case "ScriptDirectory":
-                                    scriptDir = subElement.Value;
+                                    scriptDir = subElement.Value.Replace('\\', Path.DirectorySeparatorChar);
                                     break;
                                 case "FreeMemoryArea":
                                     parser.FreeMemoryArea = ParsingUtilities.ParseHex(subElement.Value);
@@ -1336,7 +1336,7 @@ namespace STROOP.Utilities
                             switch (subElement.Name.ToString())
                             {
                                 case "HackDirectory":
-                                    hackDir = subElement.Value;
+                                    hackDir = subElement.Value.Replace('\\', Path.DirectorySeparatorChar);
                                     break;
                             }
                         }

After this fix, I faced another issue here, the loading screen was launched correctly (until the end) :

stroop_loading

But then an exception was thrown. After debugging (mono --trace:N=STROOP STROOP.exe), I found out that error :

[0x7faf18d08bc0: 21.46603 2] LEAVE: STROOP.StroopMainForm:buttonRefresh_Click (object,System.EventArgs)
[0x7faf18d08bc0: 21.46749 1] LEAVE: STROOP.StroopMainForm:StroopMainForm_Load (object,System.EventArgs)
[0x7faf18d08bc0: 21.46837 1] ENTER: STROOP.ObjectSlot:get_Text ()(this:0x7faeeb8026e0[STROOP.ObjectSlot STROOP.exe])
[0x7faf18d08bc0: 21.46837 1] LEAVE: STROOP.ObjectSlot:get_Text ()[OBJECT:(nil)]
[0x7faf18d08bc0:] EXCEPTION handling: System.ArgumentNullException: Value cannot be null.
Parameter name: s

There is a similar issue an other Github project, fixed by this commit. Apparently, Mono does not like uninitialized string. I don't know more about this. So I just replaced (stupidly) in Controls/ObjectSlot.cs the following :

--- a/STROOP/Controls/ObjectSlot.cs
+++ b/STROOP/Controls/ObjectSlot.cs
@@ -36,7 +36,7 @@ namespace STROOP
         Image _bufferedObjectImage;
         Point _textLocation = new Point();
         Point _objectImageLocation = new Point();
-        string _text;
+        string _text = "";
         int _fontHeight;
         #endregion

And this hopefully solved the error. After the loading screen, I was able to see the connection screen :

stroop_connecting

By bypassing this step, I was able to have STROOP running under GNU/Linux (in a Docker container) with Mono :

stroop_main

You can compare all the modifications on my fork :

https://github.com/SM64-TAS-ABC/STROOP/compare/Development...norbjd:6b3a768b4c0c9b0315074281ec11ec3a7e56d3b5.

norbjd commented 4 years ago

Just a little update, I've made some modifications : https://github.com/SM64-TAS-ABC/STROOP/compare/Development...norbjd:21c38cf798e402bafac085f32f8268bd17e54f53.

STROOP builds fine with Mono. As of now, I cannot connect to an emulator since there is no other process in the container, only STROOP. I'm working on this.

When bypassing the connect step, the UI works fine but no values are shown in the different tabs. I think it's the normal behavior because STROOP is not connected. But, sometimes, STROOP also hangs when clicking on tab or buttons with no particular errors.

Loading save states

I've tried to install mupen64plus (mupen64plus-ui-console on Debian) on the container along with STROOP. This runs fine, I've made a save state from here (.st2 file, renamed with extension .st). STROOP loads the save state and "works", but no values are shown anywhere. In that save state, I've collected 1 star (Big Bob-omb on the Summit), get into BoB another time, grab some coins, and did the save state. But the results in STROOP does not show anything :

stroop_file_tab

Also, you can see that objects are not shown in the part below.

@scob @onlymx13 : does loading save states is actually working on Windows with the latest version (Development branch)?

Connection to mupen64plus

Also, I'm not able to connect directly to mupen64plus process because it is not declared in Config/Config.xml (Emulators tag). I made another issue to see if you could support mupen64plus (#73). As of now, I've tried to add the following line :

<Emulator name="Mupen64Plus" processName="mupen64plus" ramStart="0x008EBA80" endianness="little"/>

(I copied ramStart value from another Emulator (mupen64-rerecording), but I'm pretty sure this won't work like this).

After rebuilding, STROOP is now able to detect the mupen64plus emulator on the connect page :

stroop_connecting_mupen64plus

But when clicking Connect, I got the following error :

Unhandled Exception:
System.EntryPointNotFoundException: CloseHandle assembly:<unknown assembly> type:<unknown type> member:(null)
  at (wrapper managed-to-native) STROOP.Utilities.Kernal32NativeMethods.CloseHandle(intptr)
  at STROOP.Utilities.Kernal32NativeMethods.CloseProcess (System.IntPtr processHandle) [0x00001] in <38a03fe220d245f3b3d5e7486135e053>:0 
  at STROOP.Utilities.WindowsProcessRamIO.Dispose (System.Boolean disposing) [0x0003f] in <38a03fe220d245f3b3d5e7486135e053>:0 
  at STROOP.Utilities.WindowsProcessRamIO.Finalize () [0x00002] in <38a03fe220d245f3b3d5e7486135e053>:0 
[ERROR] FATAL UNHANDLED EXCEPTION: System.EntryPointNotFoundException: CloseHandle assembly:<unknown assembly> type:<unknown type> member:(null)
  at (wrapper managed-to-native) STROOP.Utilities.Kernal32NativeMethods.CloseHandle(intptr)
  at STROOP.Utilities.Kernal32NativeMethods.CloseProcess (System.IntPtr processHandle) [0x00001] in <38a03fe220d245f3b3d5e7486135e053>:0 
  at STROOP.Utilities.WindowsProcessRamIO.Dispose (System.Boolean disposing) [0x0003f] in <38a03fe220d245f3b3d5e7486135e053>:0 
  at STROOP.Utilities.WindowsProcessRamIO.Finalize () [0x00002] in <38a03fe220d245f3b3d5e7486135e053>:0

The error refers to native methods (extern methods). Indeed, some of the C# files use DllImport directive :

Notably with the following DLLs :

Obviously, those DLLs could not be found on GNU/Linux environments. https://www.pinvoke.net/ can be used to find replacement to unmanaged API code to managed API code, but unfortunately, for methods I have searched for, there are no replacements that could be found.

I think that the most important files to be replaced are Utilities/Stream/ProcessStream.cs and Utilities/Kernal32NativeMethods.cs, so it may be necessary to create some for GNU/Linux equivalents.

But in a first time, if I could load a save state from STROOP, that will be a great progress. @scob @onlymx13 : do you have an idea of what can be wrong here?

Thank you very much.

onlymx13 commented 4 years ago

I don’t know too much about mupen64plus or STROOP. However, what I will ask is whether the mupen64plus .st2 files are the same format as regular mupen64 .st files. If not, they won’t work with STROOP. I think you can get the correct RAM start value using Cheat Engine somehow. Try to find the value of Mario’s coin count or something, and you could convert that into a RAM start. I’m not sure if the issues you’re experiencing with Bypass are normal or not. What kind of buttons and tabs were causing errors? I ask since for Bypass, really M64 is the only useful tab, unless you just want the address of a variable. Not showing variables is normal. I’m pretty sure regular mupen64-rr can be built for Linux; have you looked into that?

norbjd commented 4 years ago

@onlymx13 Thanks for your answer.

When using Bypass mode, or with .st2 file, the M64 tab is empty :

STROOP_bypass_m64_tab

Indeed, it can be a problem with .st2 format.

Some errors occurs sometimes when resizing the window, or clicking too fast on some different tabs.

I guess building mupen64-rr for GNU/Linux is possible, but the repo seems unmaintained : https://github.com/TASVideos/mupen64-rr (last commit it 2013). Is that this emulator? If yes, I'll take a look.

@onlymx13 Before that, could you please share with me a .st file if you have one, just to see if it works with STROOP on Linux? Thank you very much.

onlymx13 commented 4 years ago

The M64 tab is supposed to be like that until you press “Open” to open a mupen64-rr TAS file, or .m64 file. Last commit in 2013? Sadly, I’m gonna say that’s the wrong version because that’s too new. I guess it depends on which version of mupen you want. Are you in the SM64 TASing and ABC Discord server? I would advise joining that. Link is on the sidebar of https://ukikipedia.net. I don’t have an st file right now, but you could use Discord’s search to find one in that server. Some of your questions would be better put there. I’m MMMMMMMMMMMMM#2694 and scob is pannenkoek2012.

norbjd commented 4 years ago

@onlymx13 Thanks for the links. I joined quickly the SM64 TASing and ABC Discord (I'll create an account later :smile:) just to get a valid .st file that I could load in GNU/Linux STROOP :

stroop_hud

So mupen64plus format (.st2) is probably not supported (yet :smile:).

Objects are loaded in the below part, but I don't see values for Life Count, Life Display, etc. Is it normal?

The Memory tab seems to work correctly, when clicking on an object :

stroop_memory

I'll join the Discord as soon as possible and try to get the conversation continued here :smile:. Thanks again for your help :+1:, that was very appreciated.

norbjd commented 4 years ago

Also, I found last version of mupen64-rr on Github : https://github.com/SM64-TAS-ABC/mupen64-rr. Last commit had been made on Apr 14, 2018, so this is indeed a more recent version :smile:. At first glance, I think it can build on GNU/Linux and Windows, that's great news!

onlymx13 commented 4 years ago

No no no, you misunderstand. 2013 is too new. You want one from 2005 or 2006. The 2018 one might still work though. I'm not exactly sure.

norbjd commented 4 years ago

Ok, my bad, I misread that. In fact, the code was hosted here before http://code.google.com/p/mupen64-rr, and initial commit is from 2009 (not 2005 or 2006 though :thinking:). The migration to Github was done in 2013, hence this date on Github. I'll try to build an old version and see if it works, but I'm very sceptical because dependencies packages have been updated since.

norbjd commented 4 years ago

I've tried to build the version hosted on SM64-TAS-ABC Github for Linux (from the initial commit https://github.com/SM64-TAS-ABC/mupen64-rr/tree/d6cffe315be7b4d48abac2285b151cb62df12452), but without success.

I've also found the source from v0.5 (2005/08/27) and have not been able to compile it fully. Build may have succeed someday on someone's own machine, but I think there are some missing things (I've tried to tweak the build too).

I've finally found the mupen64 v0.5 Linux binary from this site : http://mupen64.emulation64.com/down.htm and was able to run it correctly along with STROOP (after installing some 32 bits dependencies). Unfortunately, the binary mupen is not recognized as one of the emulators, so I cannot connect to it with STROOP. Loading save states works "fine" though (I only see objects, not values) but like the .st file found on Discord.

I'm not sure this version of mupen is the mupen64-rerecording expected by STROOP, but that's the only I have found :man_shrugging:.

danebou commented 4 years ago

As far as the processes go. The way it works is that STROOP assumes there is a process (executable) section of PC (Windows/Linux) memory that holds the N64 RAM memory. It can either be stored in little or big endian form. So in order to get "RAW N64 memory", the process name, the offset where the N64 memory starts in the process, and the endianess type needs to be known. The offset is assumed changed whenever the emulator is re-compiled. This is is because the compiler can place the n64 memory location to wherever it wants.

So because you are recompiling, or using the linux version of mupen, the offset is going to be different regardless. You'll need to find the offset which can be calculated by finding a known address, like Mario's health, y coordinate, etc. using cheat engine. Then subtracting the N64 address of that value from the process address you just found.

norbjd commented 4 years ago

@danebou Thanks for your answer. I've made an issue (#73) related to add mupen64plus emulator, which is easier to install on GNU/Linux platforms, and also more recent than the mupen64 v0.5 I talked in my last comment (2005). I've also made a PR to support it (#74), feel free to check it.

As I said in the PR, I'm not sure it works on Windows because I don't have a Windows machine and as of now, connecting to a running process on GNU/Linux requires to rewrite some parts of the code as we have discussed on Discord. I have made a branch on my fork : https://github.com/norbjd/STROOP/tree/linux-build to rewrite functions requiring Windows only DLL to native functions (STROOP/Utilities/Kernal32NativeMethods.cs for example). You could take a look here :smile: !

norbjd commented 4 years ago

Just to keep this issue up-to-date, values are now shown in STROOP on GNU/Linux :

values_shown

The hidden values was caused by the ShowCaret and HideCaret methods of CarretlessTextBox, requiring user32.dll (not present in Mono). I have added guards around these methods calls in GNU/Linux case to avoid this unfound DLL to cause issues. See this commit : https://github.com/norbjd/STROOP/commit/094398e97813105e1d138ab75515b4a7f8aa50ad.

norbjd commented 4 years ago

Next step : rewrite extern functions relying on some DLLs

The next step is now to rewrite all methods defined as extern and requiring user32.dll or kernel32.dll (not included in Mono). This should fix other issues and maybe will allow to connect STROOP to a process running on GNU/Linux. Memory is also probably handled differently in GNU/Linux, so we must pay attention to that.

jgcodes2020 commented 2 years ago

I believe that on (?:GNU[+/])?Linux, reading another process's memory is done through /proc/<pid>/mem and /proc/<pid>/maps.

In addition, Mupen64Plus uses a heap allocation for its memory, so that's somewhat annoying.

YoshiRulz commented 2 years ago

Hi! I've been working on BizHawk's Linux port for a few years now and I'd like to offer my help. I'm in the Discord (same username) if you'd prefer to discuss there.

Some things that were helpful for BizHawk:

Some specific concerns from this thread:

danebou commented 4 months ago

I know its been a while. As far as this goes, if you still want to do the development work, then we can add linux support. For that just make many gradual PRs and we can go back and forth.

YoshiRulz commented 4 months ago

Is that directed at me or OP? I can rebase my PR if you like.

danebou commented 4 months ago

Either lol. That's sounds good. I guess what I'm saying is I'll actually monitor PRs that improve STROOP, but and I don't want a massive PR to review (not your PR, just a giant linux one).

norbjd commented 4 months ago

Hello :wave:

I've completely given up this to be honest, sorry :sweat_smile:

I only remembered it was too difficult (at least for me) to port to Linux because there were some references to Windows specific stuff, e.g. kernel32.dll. I even asked on StackOverflow to get some help:

But it seemed impossible at the time, and I really lacked C# and Windows expertise :confused:

Nevertheless, the last version I've made (https://github.com/SM64-TAS-ABC/STROOP/compare/dev...norbjd:STROOP:linux-build) was correctly loading .st files with some limitations: https://github.com/SM64-TAS-ABC/STROOP/issues/72#issuecomment-619386875.

If @YoshiRulz wants to work on this, I'll closely follow his work, but I won't have neither the time nor the expertise required to help him. But glad to see this issue revived!

danebou commented 4 months ago

You're good. Hey I'm responding to this after four years lol. Compatibility would be nice, but yeah sounded like to much of a pain to get working.

danebou commented 4 months ago

I'll close this out unless Yoshi says otherwise

YoshiRulz commented 4 months ago

I'll do what I can. I rebased my PR already.

edit: merged 2024-04-30

YoshiRulz commented 4 months ago

I drafted a similar migration for the test project, but the main project doesn't compile on Linux so I can't test it. Not that there'd be much point to it since there are only 4 test methods.

I'll work on case-sensitivity next, though I don't think I'll be able to build on Linux while there's still a WPF component in the project. Maybe I could conditionally remove it and all references to it? There are no references to it. It's unused. FFS.

danebou commented 4 months ago

Yeah WPF is just related to the Accord package used for FFMPEG to convert images to video (I think) (Nope the old decompiler view, I see, nice) .

I haven't seen any issues so far.

YoshiRulz commented 3 months ago

Figured I should give an update on this: The blocker here is being able to attach to a running process, which relies on the WindowsProcessRamIO class, which is tied to Win32 APIs as the name suggests. I recall from early EmuHawk porting efforts that we used some .dll.sos from WINE as drop-in replacements for kernel32.dll and co. That's probably still a good avenue to take, even if their OpenProcess implementation turns out to be unsuitable. And that's where I'll have to leave this for now, since I'm not experienced in process and memory management on Linux. (Alternatively, you could redesign the IPC around memory-mapped files or sockets or something, which would require buy-in from emulators but would make a Linux port relatively easy.) But before I go I'll tie up the loose ends with the project files, and if you're interested @danebou, start on modernising the codebase. To future humans: I reckon you'll need to test with standalone Mupen64Plus or maybe Dolphin—Mupen64Plus in EmuHawk and Project64 are Windows-only, as is Mupen-rr I believe, and Ares64 in EmuHawk probably won't work on either OS as it uses our Waterbox memory management system.

danebou commented 3 months ago

Nice, sounds good. Yeah modernizing is an understatement. I know pannen might be doing work here and there, but my current plan for stroop is just to let it coast (aside from community updates).

Maybe AI will eventually make pull requests lol