Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
424bd39
Write gump 9834 with an 8 digit UOP name
AsYlum- Nov 12, 2026
0fa8fdf
Keep the multi tile extra field when the multi editor saves
AsYlum- Nov 12, 2026
5bd9667
Import animation palettes starting at slot 0
AsYlum- Nov 12, 2026
1e30168
Read speech entries longer than 128 bytes in full
AsYlum- Nov 12, 2026
4b49de9
Keep the animdata tail and empty records on save
AsYlum- Nov 12, 2026
2708f78
Save skills even when the list was never opened
AsYlum- Nov 12, 2026
75e7a42
Merge radarcol CSV imports into the loaded colors
AsYlum- Nov 12, 2026
527c216
Fix static art dedup for images of different sizes
AsYlum- Nov 12, 2026
95be1bd
Close the previous client files on reload
AsYlum- Nov 12, 2026
04c5ce6
Clip animation frame runs to the frame bounds
AsYlum- Nov 12, 2026
09b2a1f
Reject land tiles that are not 44x44
AsYlum- Nov 12, 2026
5c2c295
Check texture size on single replace
AsYlum- Nov 12, 2026
6c81b78
Skip oversized images when adding items instead of throwing
AsYlum- Nov 12, 2026
220562f
Limit gump insert to ids inside the file
AsYlum- Nov 12, 2026
47e26de
Copy hue values on replace instead of sharing the object
AsYlum- Nov 12, 2026
f2752b8
Stop adding a sound when no valid file is chosen
AsYlum- Nov 12, 2026
5dd3820
Pass the multi id, not the row, to the multi change event
AsYlum- Nov 12, 2026
49a1c88
Refresh the cliloc grid after delete and insert
AsYlum- Nov 12, 2026
cfc4347
Subscribe the gump tab to events only once
AsYlum- Nov 12, 2026
23beaa0
Subscribe the animdata tab to events only once
AsYlum- Nov 12, 2026
c64795b
Load the chosen profile when Enter is pressed
AsYlum- Nov 12, 2026
a44c8bd
Tolerate missing profile attributes and plugins folder
AsYlum- Nov 12, 2026
cc4cc70
Subscribe plugins to events in Initialize, not the constructor
AsYlum- Nov 12, 2026
eaf5de6
Show the profile's plugin choice in Manage Plugins
AsYlum- Nov 12, 2026
880ef67
Load the land tile and texture lists only once
AsYlum- Nov 12, 2026
28e6c41
Serialize art reads on the shared file stream
AsYlum- Nov 12, 2026
d193e24
Preload items from a copy of the list and report errors
AsYlum- Nov 12, 2026
7e2020a
Leave multi editor shortcuts to text boxes that have focus
AsYlum- Nov 12, 2026
6c8cda2
Clear multi editor selection after undo, redo and open
AsYlum- Nov 12, 2026
333ea7b
Apply zoom to the multi editor marquee selection
AsYlum- Nov 12, 2026
c4d385a
Resolve mass import paths against the XML file's folder
AsYlum- Nov 12, 2026
4536eeb
Fix mass import index limits for hues, textures and multis
AsYlum- Nov 12, 2026
9bb2b4f
Fix inverted light cache condition
AsYlum- Nov 12, 2026
99d4d01
Load an unreadable gump file as empty instead of null
AsYlum- Nov 12, 2026
fdcbfa2
Guard sound reads against missing files and bad ids
AsYlum- Nov 12, 2026
084109c
Clear the compression flag on verdata patched UOP entries
AsYlum- Nov 12, 2026
c114a8a
Skip map diff blocks outside the map size
AsYlum- Nov 12, 2026
d4e4d97
Read blocks of a missing map UOP chunk as empty
AsYlum- Nov 12, 2026
e0f0e70
Match image file extensions case-insensitively
AsYlum- Nov 12, 2026
bef93aa
Fix off-by-one in item and land tile selection index checks
AsYlum- Nov 12, 2026
b1d49d4
Fix light preview row stride and brightness overflow
AsYlum- Nov 12, 2026
462f538
Clear directions a .vd import leaves empty
AsYlum- Nov 12, 2026
b0d4d98
Make the multi editor Dynamic flag and undo jumps undoable
AsYlum- Nov 12, 2026
04393b2
Match the UoFiddler folder casing in the release workflow
AsYlum- Nov 12, 2026
cb98578
Save the normal window bounds and restore them on any screen
AsYlum- Nov 12, 2026
2fbbcd4
Dispose the source bitmap after converting imported .bmp files
AsYlum- Nov 12, 2026
bb29543
Set the multi editor assembly version to 1.7.0
AsYlum- Nov 12, 2026
7af555a
Accept radarcol CSV rows for item ids above 0xBFFF
AsYlum- Nov 12, 2026
0b3e261
Check the sound name length in IsValidSound
AsYlum- Nov 12, 2026
a49d98d
Fix off-by-one in texture selection index check
AsYlum- Nov 12, 2026
e1c4012
Keep the extra field and Dynamic flag when pasting multi tiles
AsYlum- Nov 12, 2026
e6414d1
Count a missing map UOP chunk when measuring the map size
AsYlum- Nov 12, 2026
576836d
Match mass import .csv and .txt extensions case-insensitively
AsYlum- Nov 12, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/build-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
uses: actions/upload-artifact@v7.0.2
with:
name: ${{ env.Asset_Name }}.zip
path: ./UOFiddler/bin/Release/
path: ./UoFiddler/bin/Release/

release:
name: Release
Expand Down
39 changes: 28 additions & 11 deletions Ultima/AnimationEdit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ private static void InitializeCache()
/// </summary>
public static void Reload()
{
_fileIndex?.Dispose();
_fileIndex2?.Dispose();
_fileIndex3?.Dispose();
_fileIndex4?.Dispose();
_fileIndex5?.Dispose();
_fileIndex6?.Dispose();

_fileIndex = new FileIndex("Anim.idx", "Anim.mul", 6);
_fileIndex2 = new FileIndex("Anim2.idx", "Anim2.mul", -1);
_fileIndex3 = new FileIndex("Anim3.idx", "Anim3.mul", -1);
Expand Down Expand Up @@ -262,14 +269,19 @@ public static void LoadFromVD(int fileType, int body, BinaryReader bin)
entries[i].Extra = bin.ReadInt32();
}

foreach (Entry3D entry in entries)
for (int i = 0; i < entries.Length; ++i, ++index)
{
Entry3D entry = entries[i];
if ((entry.Lookup > 0) && (entry.Lookup < bin.BaseStream.Length) && (entry.Length > 0))
{
bin.BaseStream.Seek(entry.Lookup, SeekOrigin.Begin);
cache[index] = new AnimIdx(bin, entry.Extra);
}
++index;
else
{
// Empty in the .vd: drop what the body had there, as LoadActionFromVD does.
GetAnimation(fileType, body, i / 5, i % 5).ClearFrames();
}
}
}

Expand Down Expand Up @@ -590,26 +602,31 @@ public unsafe Bitmap[] GetFrames()

var bmp = new Bitmap(width, height, PixelFormat.Format16bppArgb1555);
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format16bppArgb1555);
var line = (ushort*)bd.Scan0;
var pixels = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;

int xBase = frame.Center.X - 0x200;
int yBase = frame.Center.Y + height - 0x200;

line += xBase;
line += yBase * delta;

// Runs are clipped to the frame, so a bad header cannot write outside the bitmap.
for (int j = 0; j < frame.RawData.Length; ++j)
{
FrameEdit.Raw raw = frame.RawData[j];

ushort* cur = line + (((raw.offsetY) * delta) + ((raw.offsetX) & 0x3FF));
ushort* end = cur + (raw.run);
int y = yBase + raw.offsetY;
if (y < 0 || y >= height)
{
continue;
}

int ii = 0;
while (cur < end)
ushort* row = pixels + (y * delta);
int x = xBase + (raw.offsetX & 0x3FF);
for (int k = 0; k < raw.run; ++k, ++x)
{
*cur++ = Palette[raw.data[ii++]];
if (x >= 0 && x < width)
{
row[x] = Palette[raw.data[k]];
}
}
}

Expand Down
61 changes: 30 additions & 31 deletions Ultima/Animations.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
Expand Down Expand Up @@ -143,9 +144,15 @@ public static AnimationFrame[] GetAnimation(int body, int action, int direction,
_streamBuffer = new byte[length];
}

_ = stream.Read(_streamBuffer, 0, length);
int read = stream.ReadAtLeast(_streamBuffer.AsSpan(0, length), length, throwOnEndOfStream: false);

var memoryStream = new MemoryStream(_streamBuffer, false);
// Too short to hold a palette and a frame count: decoding it would read stale buffer bytes.
if (read < (PaletteCapacity * 2) + 4)
{
return null;
}

var memoryStream = new MemoryStream(_streamBuffer, 0, read, false);

bool flip = direction > 4;
AnimationFrame[] frames;
Expand Down Expand Up @@ -711,54 +718,46 @@ public unsafe AnimationFrame(ushort[] palette, BinaryReader bin, bool flip)
var bmp = new Bitmap(width, height, PixelFormat.Format16bppArgb1555);
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format16bppArgb1555);
var line = (ushort*)bd.Scan0;
var pixels = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;

int header;

int xBase = xCenter - 0x200;
int yBase = (yCenter + height) - 0x200;

if (!flip)
try
{
line += xBase;
line += yBase * delta;

int header;
while ((header = bin.ReadInt32()) != 0x7FFF7FFF)
{
header ^= _doubleXor;

ushort* cur = line + ((((header >> 12) & 0x3FF) * delta) + ((header >> 22) & 0x3FF));
ushort* end = cur + (header & 0xFFF);
while (cur < end)
// Runs are clipped to the frame, so a bad header cannot write outside the bitmap.
int y = yBase + ((header >> 12) & 0x3FF);
int x = xBase + ((header >> 22) & 0x3FF);
int run = header & 0xFFF;
bool rowInside = y >= 0 && y < height;
ushort* row = pixels + (y * delta);

for (int k = 0; k < run; ++k, ++x)
{
*cur++ = palette[bin.ReadByte()];
ushort color = palette[bin.ReadByte()];
if (rowInside && x >= 0 && x < width)
{
row[flip ? width - 1 - x : x] = color;
}
}
}
}
else
finally
{
line -= xBase - width + 1;
line += yBase * delta;

while ((header = bin.ReadInt32()) != 0x7FFF7FFF)
{
header ^= _doubleXor;

ushort* cur = line + ((((header >> 12) & 0x3FF) * delta) - ((header >> 22) & 0x3FF));
ushort* end = cur - (header & 0xFFF);

while (cur > end)
{
*cur-- = palette[bin.ReadByte()];
}
}
bmp.UnlockBits(bd);
}

if (flip)
{
xCenter = width - xCenter;
}

bmp.UnlockBits(bd);

Center = new Point(xCenter, yCenter);
Bitmap = bmp;
}
Expand Down
Loading