Skip to content

Commit e231dfc

Browse files
feat: More convenient API
1 parent ca12282 commit e231dfc

5 files changed

Lines changed: 111 additions & 29 deletions

File tree

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ save.Stats.SetStat(StatId.StashGold, 2500000);
8888
save.Character.Level = 99;
8989

9090
// Write back to file
91+
File.WriteAllBytes("MyCharacter.d2s", save.ToBytes());
92+
93+
// Or write to a pre-allocated buffer for zero-copy scenarios
9194
byte[] buffer = new byte[save.EstimateSize()];
9295
int bytesWritten = save.Write(buffer);
9396
File.WriteAllBytes("MyCharacter.d2s", buffer.AsSpan(0, bytesWritten).ToArray());
@@ -258,9 +261,7 @@ The library supports converting saves between formats by specifying a target ver
258261
var save = D2Save.Read(File.ReadAllBytes("old_character.d2s"));
259262

260263
// Write as latest D2R format (version 105)
261-
byte[] buffer = new byte[save.EstimateSize()];
262-
int written = save.Write(buffer, targetVersion: 105);
263-
File.WriteAllBytes("new_character.d2s", buffer.AsSpan(0, written).ToArray());
264+
File.WriteAllBytes("new_character.d2s", save.ToBytes(targetVersion: 105));
264265
```
265266

266267
### Conversion Details

src/D2SSharp/Model/D2Save.cs

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,19 @@ public static D2Save Read(ReadOnlySpan<byte> data, IExternalData externalData)
125125
return save;
126126
}
127127

128+
/// <summary>
129+
/// Writes a save file to a new byte array.
130+
/// </summary>
131+
/// <param name="externalData">External game data for writing stats/items. Uses built-in data if null.</param>
132+
/// <param name="targetVersion">Optional target version for format conversion.</param>
133+
/// <returns>The serialized save file bytes.</returns>
134+
public byte[] ToBytes(IExternalData? externalData = null, uint? targetVersion = null)
135+
{
136+
byte[] buffer = new byte[EstimateSize()];
137+
int bytesWritten = Write(buffer, externalData ?? TxtFileExternalData.Default, targetVersion);
138+
return buffer.AsSpan(0, bytesWritten).ToArray();
139+
}
140+
128141
/// <summary>
129142
/// Writes a save file to a byte span using the default external data.
130143
/// </summary>
@@ -215,29 +228,26 @@ public int EstimateSize()
215228
// Skills: 2 + numSkills
216229
size += 2 + Character.NumSkills;
217230

218-
// Items: very variable, estimate based on item count including socketed items
219-
// Average item is ~50-200 bytes
220-
size += 4 + CountItemsRecursive(Items) * 200;
231+
// Items
232+
size += Items.EstimateSize();
221233

222234
// Corpse: header + corpses with items
223235
size += 4;
224236
foreach (var corpse in Corpses)
225237
{
226-
size += 12 + CountItemsRecursive(corpse.Items) * 200;
238+
size += 12 + corpse.Items.EstimateSize();
227239
}
228240

229241
// Expansion sections
230242
if (IsExpansion(Version))
231243
{
232-
// Merc items
233-
size += 4 + CountItemsRecursive(MercItems?.Items) * 200;
244+
// Merc items: magic(2) + items section
245+
size += 2 + (MercItems?.Items ?? new ItemsSection()).EstimateSize();
234246

235247
// Iron golem
236248
size += 3;
237249
if (IronGolem?.GolemItem != null)
238-
{
239-
size += CountItemsRecursive([IronGolem.GolemItem]) * 200;
240-
}
250+
size += 200; // Single item estimate
241251

242252
// Demon section
243253
if (Character.Preview.GameVersion == GameVersion.ReignOfTheWarlock && Demon != null)
@@ -258,23 +268,6 @@ public int EstimateSize()
258268
return (int)(size * 1.2);
259269
}
260270

261-
/// <summary>
262-
/// Counts items recursively, including socketed items.
263-
/// </summary>
264-
private static int CountItemsRecursive(IEnumerable<Item?>? items)
265-
{
266-
if (items == null) return 0;
267-
268-
int count = 0;
269-
foreach (var item in items)
270-
{
271-
if (item == null) continue;
272-
count++; // Count this item
273-
count += CountItemsRecursive(item.Sockets); // Count socketed items
274-
}
275-
return count;
276-
}
277-
278271
/// <summary>
279272
/// Verifies the checksum of save file data.
280273
/// </summary>

src/D2SSharp/Model/D2StashSave.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,34 @@ public static D2StashSave Read(ReadOnlySpan<byte> data, IExternalData externalDa
4545
return Read(ref reader, externalData);
4646
}
4747

48+
/// <summary>
49+
/// Calculates an estimated buffer size for writing all tabs.
50+
/// Returns a conservative estimate that should be large enough.
51+
/// </summary>
52+
public int EstimateSize()
53+
{
54+
int size = 0;
55+
foreach (var tab in this)
56+
{
57+
size += tab.EstimateSize();
58+
}
59+
return size;
60+
}
61+
62+
/// <summary>
63+
/// Writes all stash tabs to a new byte array.
64+
/// </summary>
65+
/// <param name="externalData">External game data for writing items. Uses built-in data if null.</param>
66+
/// <param name="targetVersion">Optional target version for format conversion.</param>
67+
/// <returns>The serialized stash save bytes.</returns>
68+
public byte[] ToBytes(IExternalData? externalData = null, uint? targetVersion = null)
69+
{
70+
byte[] buffer = new byte[EstimateSize()];
71+
var writer = new BitWriter(buffer);
72+
Write(ref writer, externalData ?? TxtFileExternalData.Default, targetVersion);
73+
return buffer.AsSpan(0, writer.BytesWritten).ToArray();
74+
}
75+
4876
/// <summary>
4977
/// Writes all stash tabs to a BitWriter using the default external data.
5078
/// </summary>

src/D2SSharp/Model/D2StashTab.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,45 @@ public static D2StashTab Read(ref BitReader reader, IExternalData externalData)
8888
return tab;
8989
}
9090

91+
/// <summary>
92+
/// Calculates an estimated buffer size for writing this tab.
93+
/// Returns a conservative estimate that should be large enough.
94+
/// </summary>
95+
public int EstimateSize()
96+
{
97+
// Header is always 64 bytes
98+
int size = (int)HeaderSize;
99+
100+
if (TabType == StashTabType.Chronicle && Chronicle != null)
101+
{
102+
// Magic(4) + Version(2) + 3 counts(6) + Reserved(8) + entries(10 each) + trailing
103+
size += 4 + 2 + 6 + 8;
104+
size += (Chronicle.SetEntries.Count + Chronicle.UniqueEntries.Count + Chronicle.RunewordEntries.Count) * 10;
105+
size += Chronicle.TrailingData.Length;
106+
}
107+
else
108+
{
109+
size += Items.EstimateSize();
110+
}
111+
112+
// Add 20% buffer for safety
113+
return (int)(size * 1.2);
114+
}
115+
116+
/// <summary>
117+
/// Writes this stash tab to a new byte array.
118+
/// </summary>
119+
/// <param name="externalData">External game data for writing items. Uses built-in data if null.</param>
120+
/// <param name="targetVersion">Optional target version for format conversion.</param>
121+
/// <returns>The serialized stash tab bytes.</returns>
122+
public byte[] ToBytes(IExternalData? externalData = null, uint? targetVersion = null)
123+
{
124+
byte[] buffer = new byte[EstimateSize()];
125+
var writer = new BitWriter(buffer);
126+
Write(ref writer, externalData ?? TxtFileExternalData.Default, targetVersion);
127+
return buffer.AsSpan(0, writer.BytesWritten).ToArray();
128+
}
129+
91130
/// <summary>
92131
/// Writes a stash tab to a BitWriter.
93132
/// </summary>

src/D2SSharp/Model/ItemsSection.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,27 @@ public static ItemsSection Read(ref BitReader reader, IExternalData externalData
3838
return section;
3939
}
4040

41+
/// <summary>
42+
/// Calculates an estimated buffer size for this items section.
43+
/// Uses 200 bytes per item (including socketed items) as a conservative estimate.
44+
/// </summary>
45+
public int EstimateSize()
46+
{
47+
return 4 + CountRecursive(this) * 200;
48+
49+
static int CountRecursive(IEnumerable<Item?> items)
50+
{
51+
int count = 0;
52+
foreach (var item in items)
53+
{
54+
if (item == null) continue;
55+
count++;
56+
count += CountRecursive(item.Sockets);
57+
}
58+
return count;
59+
}
60+
}
61+
4162
/// <summary>
4263
/// Writes an items section to a BitWriter.
4364
/// </summary>

0 commit comments

Comments
 (0)