Skip to content
Permalink
Browse files

Support for covers!

  • Loading branch information...
trishume committed May 30, 2019
1 parent 5c4fa6f commit 0c9e8be6279c1ee85e6bb9f9ff50151c2a21a467
@@ -176,35 +176,68 @@ public class Texture2DAssetData : AssetData
{
public const int ClassID = 28;

string name;
int forcedFallbackFormat;
int downscaleFallback;
int width;
int height;
int completeImageSize;
int textureFormat;
int mipCount;
bool isReadable;
bool streamingMips;

int streamingMipsPriority;
int imageCount;
int textureDimension;

int filterMode;
int anisotropic;
float mipBias;
int wrapU;
int wrapV;
int wrapW;

int lightmapFormat;
int colorSpace;
byte[] imageData;

int offset;
int size;
string path;
public string name;
public int forcedFallbackFormat;
public int downscaleFallback;
public int width;
public int height;
public int completeImageSize;
public int textureFormat;
public int mipCount;
public bool isReadable;
public bool streamingMips;

public int streamingMipsPriority;
public int imageCount;
public int textureDimension;

public int filterMode;
public int anisotropic;
public float mipBias;
public int wrapU;
public int wrapV;
public int wrapW;

public int lightmapFormat;
public int colorSpace;
public byte[] imageData;

public int offset;
public int size;
public string path;

private const int CoverPowerOfTwo = 8;
public static Texture2DAssetData CoverFromImageFile(string filePath, string levelID) {
int coverDim = 1 << CoverPowerOfTwo;
byte[] imageData = Utils.ImageFileToMipData(filePath, coverDim);
return new Texture2DAssetData() {
name = levelID + "Cover",
forcedFallbackFormat = 4,
downscaleFallback = 0,
width = coverDim,
height = coverDim,
completeImageSize = imageData.Length,
textureFormat = 3,
mipCount = CoverPowerOfTwo+1,
isReadable = false,
streamingMips = false,
streamingMipsPriority = 0,
imageCount = 1,
textureDimension = 2,
filterMode = 2,
anisotropic = 1,
mipBias = -1,
wrapU = 1,
wrapV = 1,
wrapW = 0,
lightmapFormat = 6,
colorSpace = 1,
imageData = imageData,
offset = 0,
size = 0,
path = "",
};
}

public Texture2DAssetData() {}

@@ -134,9 +134,14 @@ public class JsonLevel
public AssetPtr AddToAssets(SerializedAssets assets, Apk apk) {
// var watch = System.Diagnostics.Stopwatch.StartNew();
string levelID = LevelID();

AudioClipAssetData audioClip = CreateAudioAsset(apk, levelID);
AssetPtr audioClipPtr = assets.AppendAsset(audioClip);

string coverPath = Path.Combine(levelFolderPath, _coverImageFilename);
Texture2DAssetData cover = Texture2DAssetData.CoverFromImageFile(coverPath, levelID);
AssetPtr coverPtr = assets.AppendAsset(cover);

AssetPtr environment = new AssetPtr(20, 1); // default environment
switch(_environmentName) {
case "NiceEnvironment":
@@ -164,8 +169,7 @@ public class JsonLevel
previewDuration = _previewDuration,

audioClip = audioClipPtr,
// TODO currently $100 bills only valid in sharedassets17
coverImage = new AssetPtr(0, 18),
coverImage = coverPtr,
environment = environment,

difficultyBeatmapSets = _difficultyBeatmapSets.Select(s => s.ToAssets(assets, levelFolderPath, levelID)).Where(s => s!=null).ToList(),
@@ -7,6 +7,7 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="NVorbis" Version="0.8.6" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0006" />
</ItemGroup>

</Project>
@@ -1,6 +1,11 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.PixelFormats;

namespace LibSaberPatch
{
@@ -15,5 +20,33 @@ public static class Utils {
}
}
}

public static byte[] ImageFileToMipData(string imagePath, int topDim) {
// pre-compute size of all mips together
int totalSize = 0;
for(int dim = topDim; dim > 0; dim /= 2) {
totalSize += dim*dim;
}
totalSize *= 3; // 3 bytes per pixel

byte[] imageData = new byte[totalSize];
Span<byte> imageDataSpan = imageData;
using(Stream stream = new FileStream(imagePath, FileMode.Open)) {
using (Image<Rgb24> image = Image.Load<Rgb24>(Configuration.Default, imagePath)) {
int dataWriteIndex = 0;
for(int dim = topDim; dim > 0; dim /= 2) {
image.Mutate(x => x.Resize(dim, dim));
for (int y = 0; y < image.Height; y++) {
// need to do rows in reverse order to match what Unity wants
Span<Rgb24> rowPixels = image.GetPixelRowSpan((image.Height-1)-y);
Span<byte> rowData = MemoryMarshal.AsBytes(rowPixels);
rowData.CopyTo(imageDataSpan.Slice(dataWriteIndex, rowData.Length));
dataWriteIndex += rowData.Length;
}
}
}
}
return imageData;
}
}
}
@@ -7,9 +7,10 @@ A basic custom song patcher for Beat Saber on the Oculus Quest, written in C# an
It can patch a Beat Saber APK with new custom levels in the "Extras" folder, as well as patch the binary to not check the signature on levels. It's very similar to [emulamer's patcher](https://github.com/emulamer/QuestStopgap) with some differences:

- My library is missing some features that @emulamer's patcher has:
- Cover Art
- Adding a Custom Levels collection (mine puts it in "Extras")
- Automated batch file workflow to do all the steps.
- I have a few extra features his doesn't (note this might be outdated if he adds them):
- Cover support with proper resizing and mip-mapping for smaller patched APKs and higher frame rate on the song menu screen.
- Support for different environments as specified by the level: default, nice, triangle and big mirror
- Recursively searches for levels in a folder, or pass multiple command line arguments for levels to add
- My patcher modifies the APK in-place using zip file manipulation, eliminating the need for an unpacking and repacking step. This is probably faster but I haven't tested.
@@ -32,7 +33,7 @@ It can patch a Beat Saber APK with new custom levels in the "Extras" folder, as

### Initial setup (do this once)

1. Install .NET Core: <https://dotnet.microsoft.com/download> (for my patcher) and Java (for the signer, on Windows make sure it's 64-bit Java or the signer may not work). You may be able to avoid installing dotNET by using one of the self-contained releases on the [Releases page](https://github.com/trishume/QuestSaberPatch/releases) and following the instructions on the release, this may not have the newest code though.
1. Install .NET Core: <https://dotnet.microsoft.com/download> (for my patcher) and Java (for the signer, on Windows make sure it's 64-bit Java or the signer may not work). You may be able to avoid installing dotNET by using one of the self-contained releases on the [Releases page](https://github.com/trishume/QuestSaberPatch/releases) and following the instructions on the release, this may not have the newest features though.
2. Install `adb`: <https://developer.android.com/studio/command-line/adb>
3. Put your Oculus Quest in Developer Mode by getting your Oculus account turned into a developer account.
4. Use `adb pull /data/app/com.beatgames.beatsaber-1/base.apk {location you want to put it}` to grab the APK off the device
@@ -95,5 +95,12 @@ public class SerializedAssetTests
byte[] outData = saveData.SerializeToBinary(false);
File.WriteAllBytes(repoPath("testoutput/bubbletea_serialized.bin"), outData);
}

[Fact]
public void TestCreateCover() {
string imageFile = repoPath("testdata/bubble_tea_song/cover.jpg");
Texture2DAssetData cover = Texture2DAssetData.CoverFromImageFile(imageFile, "BUBBLETEA");
Assert.Equal(262143, cover.completeImageSize);
}
}
}

0 comments on commit 0c9e8be

Please sign in to comment.
You can’t perform that action at this time.