Skip to content

Commit

Permalink
Merge branch 'develop' into stable
Browse files Browse the repository at this point in the history
  • Loading branch information
Pathoschild committed Sep 15, 2021
2 parents 744f63c + 9c262e1 commit de0aa30
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 223 deletions.
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ OS | instruction
------- | -----------
Windows | double-click `StardewXnbHack.exe`.
Linux | execute `StardewXnbHack.sh`.
MacOS | double-click `StardewXnbHack.command`.
macOS | double-click `StardewXnbHack.command`.

## FAQs
### How does this compare to other XNB unpackers?
Expand All @@ -33,14 +33,13 @@ The main differences at a glance:
--------------------- | ---------------- | ------ | -----------
Supported asset types | ✓ images<br />✓ maps<br />✓ dictionary data<br />✓ font texture<br />✓ font XML data<br />✓ structured data | ✓ images<br />✓ maps<br />✓ dictionary data<br />✓ font textures<br />✓ font XML data<br />❑ structured data | ✓ images<br />✓ maps<br />✓ dictionary data<br />✓ font textures<br />❑ font XML data<br />❑ structured data
Export formats | ✓ `.png` for images<br />✓ `.tmx` for maps<br />✓ `.json` for data ([CP](https://stardewvalleywiki.com/Modding:Content_Patcher)-compatible) | ✓ `.png` for images<br />✓ `.tbin` for maps¹<br />❑ `.json` for data (custom format) | ✓ `.png` for images<br />✓ `.tbin` for maps¹<br />❑ `.yaml` for data
Supported platforms | ✓ Windows<br />✓ Linux²<br />✓ Mac² | ✓ Windows<br />✓ Linux<br />✓ Mac | ✓ Windows<br />❑ Linux<br />❑ Mac
Supported platforms | ✓ Windows<br />✓ Linux<br />✓ Mac | ✓ Windows<br />✓ Linux<br />✓ Mac | ✓ Windows<br />❑ Linux<br />❑ Mac
Supported operations | ✓ unpack<br />❑ pack | ✓ unpack<br />✓ pack (uncompressed) | ✓ unpack<br />✓ pack
Maintainable | ✓ easy to update | ❑ complex | ❑ complex, closed-source
Sample unpack time<br />(full `Content` folder) | ≈0m 43s | ≈6m 5s | ≈2m 20s
License | MIT | GPL | n/a

<sup`.tmx` is the [preferred map format](https://stardewvalleywiki.com/Modding:Maps#Map_formats), but you can open the `.tbin` file in Tiled and export it as `.tmx`.</sup>
<sup[Some image assets don't export correctly on Linux/Mac](https://github.com/Pathoschild/StardewXnbHack/issues/9) currently.</sup>
<sup`.tmx` is the [preferred map format](https://stardewvalleywiki.com/Modding:Maps#Map_formats), but you can open the `.tbin` file in Tiled and export it as `.tmx`.</sup>

### On Linux, the console doesn't open or shows a "Magic number is wrong" error
That's a [Mono bug with some terminals](https://github.com/mono/mono/issues/6752) in the version
Expand Down Expand Up @@ -73,17 +72,21 @@ on the wiki for the first-time setup.
2. Compile it on Windows, and create a zip file like this:
```
StardewXnbHack 1.0.0 for Windows.zip/
StardewXnbHack.dll
StardewXnbHack.exe
StardewXnbHack.runtimeconfig.json
```
3. Compile it on Linux or Mac, and create two zip files like this:
3. Compile it on Linux or macOS, and create two zip files like this:
```
StardewXnbHack 1.0.0 for Linux.zip/
StardewXnbHack.exe
StardewXnbHack
StardewXnbHack.dll
StardewXnbHack.sh
StardewXnbHack 1.0.0 for MacOS.zip/
StardewXnbHack 1.0.0 for macOS.zip/
StardewXnbHack.command
StardewXnbHack.exe
StardewXnbHack
StardewXnbHack.dll
```
4. Post a release with all three zip files.
Expand Down
12 changes: 6 additions & 6 deletions StardewXnbHack.sln
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "root", "root", "{5EDB5467-B
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F1512B6A-75A9-4425-B4EE-26D50E7075C8}.Debug|x86.ActiveCfg = Debug|x86
{F1512B6A-75A9-4425-B4EE-26D50E7075C8}.Debug|x86.Build.0 = Debug|x86
{F1512B6A-75A9-4425-B4EE-26D50E7075C8}.Release|x86.ActiveCfg = Release|x86
{F1512B6A-75A9-4425-B4EE-26D50E7075C8}.Release|x86.Build.0 = Release|x86
{F1512B6A-75A9-4425-B4EE-26D50E7075C8}.Debug|x64.ActiveCfg = Debug|x64
{F1512B6A-75A9-4425-B4EE-26D50E7075C8}.Debug|x64.Build.0 = Debug|x64
{F1512B6A-75A9-4425-B4EE-26D50E7075C8}.Release|x64.ActiveCfg = Release|x64
{F1512B6A-75A9-4425-B4EE-26D50E7075C8}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
16 changes: 4 additions & 12 deletions StardewXnbHack/Framework/PlatformContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public bool TryDetectGamePaths(string specifiedPath, out string gamePath, out st
}

// if game folder exists without a content folder, track the first found game path (i.e. the highest-priority one)
gamePath = gamePath ?? curGamePath;
gamePath ??= curGamePath;
}

return false;
Expand Down Expand Up @@ -92,8 +92,8 @@ private string TryGamePath(string path)
return null;

// has game files
bool hasExecutable = File.Exists(Path.Combine(gameDir.FullName, this.GetExecutableFileName()));
if (!hasExecutable)
bool hasGameDll = File.Exists(Path.Combine(gameDir.FullName, "Stardew Valley.dll"));
if (!hasGameDll)
return null;

// isn't the build folder when compiled directly
Expand All @@ -104,14 +104,6 @@ private string TryGamePath(string path)
return gameDir.FullName;
}

/// <summary>Get the filename for the Stardew Valley executable.</summary>
private string GetExecutableFileName()
{
return this.Platform == Platform.Windows
? "Stardew Valley.exe"
: "StardewValley.exe";
}

/// <summary>Get the absolute path to the content folder for a given game, if found.</summary>
/// <param name="gamePath">The absolute path to the game folder.</param>
private string FindContentPath(string gamePath)
Expand All @@ -136,7 +128,7 @@ private IEnumerable<string> GetPossibleRelativeContentPaths()
if (this.Platform != Platform.Mac)
yield return "Content";

// MacOS
// macOS
else
{
// Steam paths
Expand Down
15 changes: 0 additions & 15 deletions StardewXnbHack/Framework/PlatformExtensions.cs

This file was deleted.

106 changes: 9 additions & 97 deletions StardewXnbHack/Framework/Writers/SpriteFontWriter.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Toolkit.Utilities;
Expand Down Expand Up @@ -34,17 +30,15 @@ public override bool TryWriteFile(object asset, string toPathWithoutExtension, s
SpriteFont font = (SpriteFont)asset;

// get texture
Texture2D texture = platform == Platform.Windows
? this.RequireField<Texture2D>(font, "textureValue")
: this.RequireProperty<Texture2D>(font, "Texture");
Texture2D texture = font.Texture;

// save texture
using (Stream stream = File.Create($"{toPathWithoutExtension}.png"))
{
if (platform.IsMono() && texture.Format == SurfaceFormat.Dxt3) // MonoGame can't read DXT3 textures directly, need to export through GPU
if (texture.Format == SurfaceFormat.Dxt3) // MonoGame can't read DXT3 textures directly, need to export through GPU
{
using (RenderTarget2D renderTarget = this.RenderWithGpu(texture))
renderTarget.SaveAsPng(stream, texture.Width, texture.Height);
using RenderTarget2D renderTarget = this.RenderWithGpu(texture);
renderTarget.SaveAsPng(stream, texture.Width, texture.Height);
}
else
texture.SaveAsPng(stream, texture.Width, texture.Height);
Expand All @@ -57,7 +51,7 @@ public override bool TryWriteFile(object asset, string toPathWithoutExtension, s
font.Spacing,
font.DefaultCharacter,
font.Characters,
Glyphs = this.GetGlyphs(font, platform)
Glyphs = font.GetGlyphs()
};
File.WriteAllText($"{toPathWithoutExtension}.{this.GetDataExtension()}", this.FormatData(data));

Expand All @@ -69,86 +63,6 @@ public override bool TryWriteFile(object asset, string toPathWithoutExtension, s
/*********
** Private methods
*********/
/// <summary>Get the font glyph data for a MonoGame font.</summary>
/// <param name="font">The sprite font.</param>
/// <param name="platform">The operating system running the unpacker.</param>
private IDictionary<char, object> GetGlyphs(SpriteFont font, Platform platform)
{
IDictionary<char, object> glyphs = new Dictionary<char, object>();

if (platform == Platform.Windows)
{
// get internal sprite data
IList<Rectangle> glyphData = this.RequireField<List<Rectangle>>(font, "glyphData");
IList<Rectangle> croppingData = this.RequireField<List<Rectangle>>(font, "croppingData");
IList<Vector3> kerning = this.RequireField<List<Vector3>>(font, "kerning");

// replicate MonoGame structure for consistency (and readability)
for (int i = 0; i < font.Characters.Count; i++)
{
char ch = font.Characters[i];
glyphs[ch] = new
{
BoundsInTexture = glyphData[i],
Cropping = croppingData[i],
Character = ch,

LeftSideBearing = kerning[i].X,
Width = kerning[i].Y,
RightSideBearing = kerning[i].Z,

WidthIncludingBearings = kerning[i].X + kerning[i].Y + kerning[i].Z
};
}
}
else
{
foreach (DictionaryEntry entry in this.InvokeRequiredMethod<IDictionary>(font, "GetGlyphs")) // method is public in Mono, but need reflection so code compiles on Windows
glyphs[(char)entry.Key] = entry.Value;
}

return glyphs;
}

/// <summary>Get a required font field using reflection.</summary>
/// <typeparam name="T">The field type.</typeparam>
/// <param name="font">The font instance for which to get a value.</param>
/// <param name="name">The field name.</param>
private T RequireField<T>(SpriteFont font, string name)
{
FieldInfo field = typeof(SpriteFont).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (field == null)
throw new InvalidOperationException($"Can't access {nameof(SpriteFont)}.{name} field");

return (T)field.GetValue(font);
}

/// <summary>Get a required font property using reflection.</summary>
/// <typeparam name="T">The field type.</typeparam>
/// <param name="font">The font instance for which to get a value.</param>
/// <param name="name">The field name.</param>
private T RequireProperty<T>(SpriteFont font, string name)
{
PropertyInfo property = typeof(SpriteFont).GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (property == null)
throw new InvalidOperationException($"Can't access {nameof(SpriteFont)}.{name} property");

return (T)property.GetValue(font);
}

/// <summary>Invoke a required font method using reflection.</summary>
/// <typeparam name="TReturn">The return type.</typeparam>
/// <param name="font">The font instance for which to get a method.</param>
/// <param name="name">The method name.</param>
private TReturn InvokeRequiredMethod<TReturn>(SpriteFont font, string name)
{
MethodInfo method = typeof(SpriteFont).GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (method == null)
throw new InvalidOperationException($"Can't access {nameof(SpriteFont)}.{name} method");

return (TReturn)method.Invoke(font, null);
}

/// <summary>Draw a texture to a GPU render target.</summary>
/// <param name="texture">The texture to draw.</param>
private RenderTarget2D RenderWithGpu(Texture2D texture)
Expand All @@ -163,12 +77,10 @@ private RenderTarget2D RenderWithGpu(Texture2D texture)
{
gpu.Clear(Color.Transparent); // set transparent background

using (SpriteBatch batch = new SpriteBatch(gpu))
{
batch.Begin();
batch.Draw(texture, Vector2.Zero, Color.White);
batch.End();
}
using SpriteBatch batch = new SpriteBatch(gpu);
batch.Begin();
batch.Draw(texture, Vector2.Zero, Color.White);
batch.End();
}
finally
{
Expand Down
Loading

0 comments on commit de0aa30

Please sign in to comment.