- 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\
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:
with
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:
That should do it, ServUO migrated to Net8... Why? 30-40% faster right out of the box.
The [restart command also...
12. In Main.cs
Replace this method: public static void Kill(bool restart)
With the same method expanded:
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
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("<");
break;
case '>':
sb.Append(">");
break;
case '&':
sb.Append("&");
break;
case '"':
sb.Append(""");
break;
case '\'':
sb.Append("'");
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("<");
break;
case '>':
sb.Append(">");
break;
case '&':
sb.Append("&");
break;
case '"':
sb.Append(""");
break;
case '\'':
sb.Append("'");
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);
}
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.
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();
}
ServUO-Pub57-Net8.7z
Annd for those of us weary... here's a github repository.
GitHub - IWerkWerk/ServUO-Pub57-Net8-Migrated
Last edited: