iWerking

Member
ServUO Version
Publish 57
Ultima Expansion
Endless Journey
1. Unload Server
1.1 Double click unloaded server to open .csproj file
1.2
Replace:
<TargetFramework>net48</TargetFramework>
to
<TargetFramework>net8.0</TargetFramework>

2. Unload Scripts
2.1 Double click unloaded scripts to open .csproj file
2.2
Replace:
<TargetFramework>net48</TargetFramework>
to
<TargetFramework>net8.0</TargetFramework>

3. Unload Scripts
3.1 Double click unloaded scripts to open .csproj file
3.2 Replace:
<TargetFramework>net48</TargetFramework>
to
<TargetFramework>net8.0</TargetFramework>

4. Since System.Web got removed in Net5, have to rewrite the classes the framework uses.
4.1 Put the following code into: Scripts\Services\Reports\Rendering\

HTMLTextWriter Replacement:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Server.Engines.Reports
{
    // Replacement for System.Web.UI.HtmlTextWriterAttribute
    public enum HtmlTextWriterAttribute
    {
        Align,
        Alt,
        Background,
        Bgcolor,
        Border,
        Cellpadding,
        Cellspacing,
        Class,
        Colspan,
        Height,
        Href,
        Id,
        Onclick,
        Rel,
        Src,
        Style,
        Title,
        Type,
        Width
    }

    // Replacement for System.Web.UI.HtmlTextWriterTag
    public enum HtmlTextWriterTag
    {
        A,
        Body,
        Br,
        Center,
        Div,
        Head,
        Html,
        Img,
        Link,
        Table,
        Td,
        Title,
        Tr
    }

    // Replacement for System.Web.UI.HtmlTextWriter
    public class HtmlTextWriter : IDisposable
    {
        private readonly TextWriter m_Writer;
        private readonly string m_TabString;
        private int m_IndentLevel;
        private bool m_TagOpen;
        private readonly Stack<string> m_TagStack;

        public HtmlTextWriter(TextWriter writer, string tabString = "\t")
        {
            m_Writer = writer ?? throw new ArgumentNullException(nameof(writer));
            m_TabString = tabString ?? string.Empty;
            m_IndentLevel = 0;
            m_TagOpen = false;
            m_TagStack = new Stack<string>();
        }

        public void AddAttribute(HtmlTextWriterAttribute key, string value)
        {
            AddAttribute(GetAttributeName(key), value);
        }

        public void AddAttribute(string name, string value)
        {
            if (m_TagOpen)
            {
                m_Writer.Write(" {0}=\"{1}\"", name, EncodeAttribute(value));
            }
        }

        public void RenderBeginTag(HtmlTextWriterTag tagKey)
        {
            RenderBeginTag(GetTagName(tagKey));
        }

        public void RenderBeginTag(string tagName)
        {
            if (m_TagOpen)
            {
                m_Writer.Write(">");
                m_Writer.WriteLine();
                m_TagOpen = false;
            }

            WriteIndent();
            m_Writer.Write("<{0}", tagName);
            m_TagOpen = true;
            m_TagStack.Push(tagName);
            m_IndentLevel++;
        }

        public void RenderEndTag()
        {
            if (m_TagStack.Count == 0)
                return;

            string tagName = m_TagStack.Pop();
            if (m_TagOpen)
            {
                m_Writer.Write(" />");
                m_Writer.WriteLine();
                m_TagOpen = false;
            }
            else
            {
                m_IndentLevel--;
                WriteIndent();
                m_Writer.Write("</{0}>", tagName);
                m_Writer.WriteLine();
            }
            m_IndentLevel = Math.Max(0, m_IndentLevel);
        }

        public void RenderEndTag(HtmlTextWriterTag tagKey)
        {
            RenderEndTag(GetTagName(tagKey));
        }

        public void RenderEndTag(string tagName)
        {
            if (m_TagOpen && m_TagStack.Count > 0 && m_TagStack.Peek() == tagName)
            {
                m_Writer.Write(" />");
                m_Writer.WriteLine();
                m_TagOpen = false;
                m_TagStack.Pop();
            }
            else
            {
                // Remove from stack if it's there
                if (m_TagStack.Count > 0 && m_TagStack.Peek() == tagName)
                {
                    m_TagStack.Pop();
                }
                m_IndentLevel--;
                WriteIndent();
                m_Writer.Write("</{0}>", tagName);
                m_Writer.WriteLine();
            }
            m_IndentLevel = Math.Max(0, m_IndentLevel);
        }

        public void Write(string text)
        {
            if (m_TagOpen)
            {
                m_Writer.Write(">");
                m_TagOpen = false;
            }
            m_Writer.Write(EncodeHtml(text));
        }

        public void Write(string format, params object[] args)
        {
            Write(string.Format(format, args));
        }

        public void WriteLine()
        {
            if (m_TagOpen)
            {
                m_Writer.Write(">");
                m_TagOpen = false;
            }
            m_Writer.WriteLine();
        }

        private void WriteIndent()
        {
            for (int i = 0; i < m_IndentLevel; i++)
            {
                m_Writer.Write(m_TabString);
            }
        }

        private string EncodeHtml(string text)
        {
            if (string.IsNullOrEmpty(text))
                return text;

            StringBuilder sb = new StringBuilder(text.Length);
            foreach (char c in text)
            {
                switch (c)
                {
                    case '<':
                        sb.Append("&lt;");
                        break;
                    case '>':
                        sb.Append("&gt;");
                        break;
                    case '&':
                        sb.Append("&amp;");
                        break;
                    case '"':
                        sb.Append("&quot;");
                        break;
                    case '\'':
                        sb.Append("&#39;");
                        break;
                    default:
                        sb.Append(c);
                        break;
                }
            }
            return sb.ToString();
        }

        private string EncodeAttribute(string value)
        {
            if (string.IsNullOrEmpty(value))
                return value;

            StringBuilder sb = new StringBuilder(value.Length);
            foreach (char c in value)
            {
                switch (c)
                {
                    case '<':
                        sb.Append("&lt;");
                        break;
                    case '>':
                        sb.Append("&gt;");
                        break;
                    case '&':
                        sb.Append("&amp;");
                        break;
                    case '"':
                        sb.Append("&quot;");
                        break;
                    case '\'':
                        sb.Append("&#39;");
                        break;
                    default:
                        sb.Append(c);
                        break;
                }
            }
            return sb.ToString();
        }

        private string GetAttributeName(HtmlTextWriterAttribute attr)
        {
            return attr.ToString().ToLowerInvariant();
        }

        private string GetTagName(HtmlTextWriterTag tag)
        {
            return tag.ToString().ToLowerInvariant();
        }

        public void Dispose()
        {
            if (m_TagOpen)
            {
                m_Writer.Write(">");
                m_TagOpen = false;
            }
        }
    }
}

5. Inside HtmlRenderer located at Scripts\Services\Reports\Rendering\
change:

using System.Web.UI;
using HtmlAttr = System.Web.UI.HtmlTextWriterAttribute;
using HtmlTag = System.Web.UI.HtmlTextWriterTag;
to
using HtmlAttr = Server.Engines.Reports.HtmlTextWriterAttribute;
using HtmlTag = Server.Engines.Reports.HtmlTextWriterTag;

6. Inside app.config located at Server\
change

<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/></startup></configuration>
to
<configuration>
</configuration>

7. In ServUO.exe.config
change

<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/></startup></configuration>
to
<configuration>
</configuration>

8. There should be about 50 errors now pertaining to the missing libraries and dependencies.
8.1 Right-click your project solutions and go to 'Manage Nuget Packages'.
8.2 Right-click Ultima, go to 'Manage Nuget Packages', search for: System.Drawing.Common

8.3 Right-click Server, go to 'Manage Nuget Packages', search for: System.Diagnostics.PerformanceCounter

9. Right-click Scripts project solution, go to 'Properties'
9.1 Assign the 'Target OS' to be windows (is this why ServUO has not updated, cross-platform compatibility?)

10. There should be around 5 errors now about Emitter.cs, Appdomain, and saving .dlls
10.1 Inside Scripts\Misc\Emitter.cs, replace:

DynamicModule:
            this.m_AssemblyBuilder = this.m_AppDomain.DefineDynamicAssembly(
                new AssemblyName(assemblyName),
                canSave ? AssemblyBuilderAccess.RunAndSave : AssemblyBuilderAccess.Run);

            if (canSave)
            {
                this.m_ModuleBuilder = this.m_AssemblyBuilder.DefineDynamicModule(
                    assemblyName,
                    String.Format("{0}.dll", assemblyName.ToLower()),
                    false);
            }
            else
            {
                this.m_ModuleBuilder = this.m_AssemblyBuilder.DefineDynamicModule(
                    assemblyName,
                    false);
            }
with
DynamicModule-Fix:
            // In .NET Core/.NET 5+, use AssemblyBuilder.DefineDynamicAssembly instead of AppDomain.DefineDynamicAssembly
            // Note: Saving dynamic assemblies to disk is not supported in .NET Core/.NET 5+
            // AssemblyBuilderAccess.RunAndSave doesn't exist, so we always use Run
            this.m_AssemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(
                new AssemblyName(assemblyName),
                AssemblyBuilderAccess.Run);

            // DefineDynamicModule signature changed in .NET Core/.NET 5+
            // The overload with just name is available
            this.m_ModuleBuilder = this.m_AssemblyBuilder.DefineDynamicModule(assemblyName);



11. You should get a runtime crash at this point,
11.1 because console commands no longer work through BeginInvoke.
11.2 These need to be executed through a Task
11.3 Inside Scripts\Misc\ConsoleCommands.cs
11.4 Add at the header:
using System.Threading.Tasks;
11.5
Inside:
private static void PollCommands()
replace:
_Listen.BeginInvoke(r => ProcessInput(_Listen.EndInvoke(r)), null);

with:
// Use Task.Run instead of BeginInvoke/EndInvoke (not supported in .NET 8)
Task.Run(() => ProcessInput(_Listen()));

11.6
Inside:
private static void ProcessCommand()
replace:
_Listen.BeginInvoke(r => ProcessInput(_Listen.EndInvoke(r)), null);
with:
// Use Task.Run instead of BeginInvoke/EndInvoke (not supported in .NET 8)
Task.Run(() => ProcessInput(_Listen()));


At this point, you should start the application and get a runtime crash about missing host policy.
This is because a .json file isn't being copied at buildtime to the root directory because it never had to before I guess.

Add this to the Server.csproj file:
HostPolicy-Fix:
<Target Name="CopyRuntimeConfig" AfterTargets="Build">
        <PropertyGroup>
            <!-- Source paths to check (in order of likelihood) -->
            <BinDebugPath>$(MSBuildProjectDirectory)\bin\Debug\$(AssemblyName).runtimeconfig.json</BinDebugPath>
            <BinReleasePath>$(MSBuildProjectDirectory)\bin\Release\$(AssemblyName).runtimeconfig.json</BinReleasePath>
            <BinConfigPath>$(MSBuildProjectDirectory)\bin\$(Configuration)\$(AssemblyName).runtimeconfig.json</BinConfigPath>
            <OutputPathFile>$(OutputPath)$(AssemblyName).runtimeconfig.json</OutputPathFile>
            <!-- Destination: root directory where ServUO.exe lives -->
            <RuntimeConfigDest>$(MSBuildProjectDirectory)\..\$(AssemblyName).runtimeconfig.json</RuntimeConfigDest>
        </PropertyGroup>
        <!-- Try to copy from bin\Debug first -->
        <Copy SourceFiles="$(BinDebugPath)" DestinationFiles="$(RuntimeConfigDest)" Condition="Exists('$(BinDebugPath)')" ContinueOnError="false" />
        <!-- Try bin\Release if Debug didn't work -->
        <Copy SourceFiles="$(BinReleasePath)" DestinationFiles="$(RuntimeConfigDest)" Condition="!Exists('$(RuntimeConfigDest)') And Exists('$(BinReleasePath)')" ContinueOnError="false" />
        <!-- Try bin\$(Configuration) as fallback -->
        <Copy SourceFiles="$(BinConfigPath)" DestinationFiles="$(RuntimeConfigDest)" Condition="!Exists('$(RuntimeConfigDest)') And Exists('$(BinConfigPath)')" ContinueOnError="false" />
        <!-- Try OutputPath as last resort -->
        <Copy SourceFiles="$(OutputPathFile)" DestinationFiles="$(RuntimeConfigDest)" Condition="!Exists('$(RuntimeConfigDest)') And Exists('$(OutputPathFile)')" ContinueOnError="false" />
        <Message Text="Copied ServUO.runtimeconfig.json to root directory: $(RuntimeConfigDest)" Condition="Exists('$(RuntimeConfigDest)')" Importance="normal" />
        <Warning Text="ServUO.runtimeconfig.json not found. Searched: $(BinDebugPath), $(BinReleasePath), $(BinConfigPath), $(OutputPathFile)" Condition="!Exists('$(RuntimeConfigDest)')" />
    </Target>



That should do it, ServUO migrated to Net8... Why? 30-40% faster right out of the box.:D

The [restart command also...
12. In Main.cs
Replace this method: public static void Kill(bool restart)

Kill()-Restart-Replacement:
        public static void Kill(bool restart)
        {
            HandleClosed();
            if (restart)
            {
                Process.Start(ExePath, Arguments);
            }
            Process.Kill();
        }

With the same method expanded:

Kill-Restart-Replacement-2:
       public static void Kill(bool restart)
        {
            HandleClosed();

            if (restart)
            {
                // In .NET 8, framework-dependent apps need to be run with 'dotnet' command
                // instead of directly launching the .exe
                try
                {
                    ProcessStartInfo startInfo = new ProcessStartInfo
                    {
                        FileName = "dotnet",
                        Arguments = $"\"{ExePath}\" {Arguments}".TrimEnd(),
                        WorkingDirectory = BaseDirectory,
                        UseShellExecute = true
                    };
                    Process.Start(startInfo);
                }
                catch
                {
                    // Fallback: try direct execution if dotnet command fails
                    try
                    {
                        Process.Start(ExePath, Arguments);
                    }
                    catch
                    {
                        // If both fail, log error but continue with shutdown
                        Console.WriteLine("Warning: Failed to restart application. Please restart manually.");
                    }
                }
            }

            Process.Kill();
        }
And for those of us way too... busy... to figure this out, here's the upload for it:
ServUO-Pub57-Net8.7z

Annd for those of us weary... here's a github repository.
GitHub - IWerkWerk/ServUO-Pub57-Net8-Migrated
 
Last edited:

Active Shards

Donations

Total amount
$0.00
Goal
$500.00
Back