Compare commits

...

8 Commits

14 changed files with 578 additions and 831 deletions

View File

@ -2,6 +2,7 @@ using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using Omegasis.SaveBackup.Framework;
using StardewModdingAPI;
using StardewModdingAPI.Events;
@ -15,16 +16,16 @@ namespace Omegasis.SaveBackup
** Fields
*********/
/// <summary>The folder path containing the game's app data.</summary>
private static readonly string AppDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley");
private static string AppDataPath => Constants.TargetPlatform != GamePlatform.Android ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley") : Constants.DataPath;
/// <summary>The folder path containing the game's saves.</summary>
private static readonly string SavesPath = Path.Combine(SaveBackup.AppDataPath, "Saves");
private static string SavesPath => Constants.TargetPlatform != GamePlatform.Android ? Path.Combine(SaveBackup.AppDataPath, "Saves") : Constants.CurrentSavePath;
/// <summary>The folder path containing backups of the save before the player starts playing.</summary>
private static readonly string PrePlayBackupsPath = Path.Combine(SaveBackup.AppDataPath, "Backed_Up_Saves", "Pre_Play_Saves");
/// <summary>The folder path containing nightly backups of the save.</summary>
private static readonly string NightlyBackupsPath = Path.Combine(SaveBackup.AppDataPath, "Backed_Up_Saves", "Nightly_InGame_Saves");
private static string NightlyBackupsPath => Constants.TargetPlatform != GamePlatform.Android ? Path.Combine(SaveBackup.AppDataPath, "Backed_Up_Saves", "Nightly_InGame_Saves") : Path.Combine(SaveBackup.AppDataPath, "Backed_Up_Saves", Constants.SaveFolderName, "Nightly_InGame_Saves");
/// <summary>The mod configuration.</summary>
private ModConfig Config;
@ -39,7 +40,8 @@ namespace Omegasis.SaveBackup
{
this.Config = helper.ReadConfig<ModConfig>();
this.BackupSaves(SaveBackup.PrePlayBackupsPath);
if(Constants.TargetPlatform != GamePlatform.Android)
this.BackupSaves(SaveBackup.PrePlayBackupsPath);
helper.Events.GameLoop.Saving += this.OnSaving;
}
@ -56,21 +58,108 @@ namespace Omegasis.SaveBackup
this.BackupSaves(SaveBackup.NightlyBackupsPath);
}
/// <summary>Recursively copy a directory or file.</summary>
/// <param name="source">The file or folder to copy.</param>
/// <param name="targetFolder">The folder to copy into.</param>
/// <param name="copyRoot">Whether to copy the root folder itself, or <c>false</c> to only copy its contents.</param>
/// <param name="filter">A filter which matches the files or directories to copy, or <c>null</c> to copy everything.</param>
/// <remarks>Derived from the SMAPI installer code.</remarks>
/// <returns>Returns whether any files were copied.</returns>
private bool RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder, bool copyRoot = true)
{
if (!source.Exists)
return false;
bool anyCopied = false;
switch (source)
{
case FileInfo sourceFile:
targetFolder.Create();
sourceFile.CopyTo(Path.Combine(targetFolder.FullName, sourceFile.Name));
anyCopied = true;
break;
case DirectoryInfo sourceDir:
DirectoryInfo targetSubfolder = copyRoot ? new DirectoryInfo(Path.Combine(targetFolder.FullName, sourceDir.Name)) : targetFolder;
foreach (var entry in sourceDir.EnumerateFileSystemInfos())
anyCopied = this.RecursiveCopy(entry, targetSubfolder) || anyCopied;
break;
default:
throw new NotSupportedException($"Unknown filesystem info type '{source.GetType().FullName}'.");
}
return anyCopied;
}
/// <summary>Create a zip using the .NET compression library.</summary>
/// <param name="sourcePath">The file or directory path to zip.</param>
/// <param name="destination">The destination file to create.</param>
/// <exception cref="NotSupportedException">The compression libraries aren't available on this system.</exception>
private void CompressUsingNetFramework(string sourcePath, FileInfo destination)
{
// get compress method
MethodInfo createFromDirectory;
try
{
// create compressed backup
Assembly coreAssembly = Assembly.Load("System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly.");
Assembly fsAssembly = Assembly.Load("System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly.");
Type compressionLevelType = coreAssembly.GetType("System.IO.Compression.CompressionLevel") ?? throw new InvalidOperationException("Can't load CompressionLevel type.");
Type zipFileType = fsAssembly.GetType("System.IO.Compression.ZipFile") ?? throw new InvalidOperationException("Can't load ZipFile type.");
createFromDirectory = zipFileType.GetMethod("CreateFromDirectory", new[] { typeof(string), typeof(string), compressionLevelType, typeof(bool) }) ?? throw new InvalidOperationException("Can't load ZipFile.CreateFromDirectory method.");
}
catch (Exception ex)
{
throw new NotSupportedException("Couldn't load the .NET compression libraries on this system.", ex);
}
// compress file
createFromDirectory.Invoke(null, new object[] { sourcePath, destination.FullName, CompressionLevel.Fastest, false });
}
private void OriginCompressLogic(string folderPath)
{
ZipFile.CreateFromDirectory(SaveBackup.SavesPath, Path.Combine(folderPath, $"backup-{DateTime.Now:yyyyMMdd'-'HHmmss}.zip"));
}
/// <summary>Back up saves to the specified folder.</summary>
/// <param name="folderPath">The folder path in which to generate saves.</param>
private void BackupSaves(string folderPath)
{
// back up saves
Directory.CreateDirectory(folderPath);
ZipFile.CreateFromDirectory(SaveBackup.SavesPath, Path.Combine(folderPath, $"backup-{DateTime.Now:yyyyMMdd'-'HHmmss}.zip"));
if(Constants.TargetPlatform == GamePlatform.Android)
{
FileInfo targetFile = new FileInfo(Path.Combine(folderPath, $"backup-{DateTime.Now:yyyyMMdd'-'HHmmss}.zip"));
try
{
this.CompressUsingNetFramework(folderPath, targetFile);
}
catch (NotSupportedException)
{
this.RecursiveCopy(new DirectoryInfo(SaveBackup.SavesPath), new DirectoryInfo(Path.Combine(folderPath, $"backup-{DateTime.Now:yyyyMMdd'-'HHmmss}")), false);
}
}
else
{
this.OriginCompressLogic(folderPath);
}
// delete old backups
new DirectoryInfo(folderPath)
.EnumerateFiles()
.GetFileSystemInfos()
.OrderByDescending(f => f.CreationTime)
.Skip(this.Config.SaveCount)
.ToList()
.ForEach(file => file.Delete());
.ForEach(file =>
{
if (file is DirectoryInfo folder)
folder.Delete(recursive: true);
else
file.Delete();
});
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SimpleSoundManager.Framework.Music
{
/// <summary>
/// Config class for the mod.
/// </summary>
public class Config
{
public bool EnableDebugLog;
/// <summary>
/// Constructor.
/// </summary>
public Config()
{
EnableDebugLog = false;
}
}
}

View File

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Timers;
using Microsoft.Xna.Framework.Audio;
using StardewValley;
namespace SimpleSoundManager.Framework
{
/// <summary>Manages all music for the mod.</summary>
public class MusicManager
{
/*********
** Fields
*********/
/// <summary>The RNG used to select music packs and songs.</summary>
private readonly Random Random = new Random();
/// <summary>The delay timer between songs.</summary>
private readonly Timer Timer = new Timer();
/*********
** Accessors
*********/
/// <summary>The loaded music packs.</summary>
public IDictionary<string, MusicPack> MusicPacks { get; } = new Dictionary<string, MusicPack>();
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
public MusicManager()
{
}
/// <summary>Adds a valid xwb music pack to the list of music packs available.</summary>
/// <param name="musicPack">The music pack to add.</param>
/// <param name="displayLogInformation">Whether or not to display the process to the console. Will include information from the pack's metadata. Default:False</param>
/// <param name="displaySongs">If displayLogInformation is also true this will display the name of all of the songs in the music pack when it is added in.</param>
public void addMusicPack(MusicPack musicPack, bool displayLogInformation = false, bool displaySongs = false)
{
if (displayLogInformation)
{
if (ModCore.Config.EnableDebugLog)
{
ModCore.ModMonitor.Log("Adding music pack:");
ModCore.ModMonitor.Log($" Name: {musicPack.Name}");
ModCore.ModMonitor.Log($" Author: {musicPack.Manifest.Author}");
ModCore.ModMonitor.Log($" Description: {musicPack.Manifest.Description}");
ModCore.ModMonitor.Log($" Version Info: {musicPack.Manifest.Version}");
}
if (displaySongs && ModCore.Config.EnableDebugLog)
{
ModCore.ModMonitor.Log(" Song List:");
foreach (string songName in musicPack.Sounds.Keys)
ModCore.ModMonitor.Log($" {songName}");
}
}
this.MusicPacks.Add(musicPack.Name, musicPack);
}
/// <summary>
/// Plays the specified sound from the music pack.
/// </summary>
/// <param name="packName"></param>
/// <param name="soundName"></param>
public void playSound(string packName, string soundName)
{
if (this.MusicPacks.ContainsKey(packName))
{
this.MusicPacks[packName].PlaySound(soundName);
}
else
{
ModCore.DebugLog("No pack with specified key/name: " + packName);
}
}
/// <summary>
/// Stops a said sound from the music pack.
/// </summary>
/// <param name="packName"></param>
/// <param name="soundName"></param>
public void stopSound(string packName,string soundName)
{
if (this.MusicPacks.ContainsKey(packName))
{
this.MusicPacks[packName].StopSound();
}
else
{
ModCore.DebugLog("No pack with specified key/name: " + packName);
}
}
/// <summary>
/// Updates all music packs every so often.
/// </summary>
public void update()
{
foreach(MusicPack pack in this.MusicPacks.Values)
{
pack.update();
}
}
}
}

View File

@ -0,0 +1,224 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using NAudio.Vorbis;
using NAudio.Wave;
using StardewModdingAPI;
using StardewValley;
namespace SimpleSoundManager.Framework
{
/// <summary>A content pack which can provide music and sounds.</summary>
public class MusicPack
{
/*********
** Fields
*********/
/// <summary>The name of the folder which contains the saved player settings.</summary>
private readonly string DataFolderName = "data";
/// <summary>The name of the folder which contains available music.</summary>
private readonly string MusicFolderName = "music";
/*********
** Accessors
*********/
/// <summary>The underlying content pack.</summary>
public IContentPack ContentPack { get; }
/// <summary>The current song name being played, if any.</summary>
public string lastPlayedSoundName { get; private set; }
/// <summary>The currently sound being played, if any.</summary>
public SoundEffectInstance lastPlayedSound { get; private set; }
public Dictionary<string, List<SoundEffectInstance>> playingSounds;
/// <summary>The manifest info.</summary>
public IManifest Manifest => this.ContentPack.Manifest;
/// <summary>The name of the music pack.</summary>
public string Name => this.ContentPack.Manifest.Name;
/// <summary>The available sounds.</summary>
public Dictionary<string, SoundEffectInstance> Sounds { get; } = new Dictionary<string, SoundEffectInstance>();
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="contentPack">The underlying content pack.</param>
public MusicPack(IContentPack contentPack)
{
this.ContentPack = contentPack;
this.playingSounds = new Dictionary<string, List<SoundEffectInstance>>();
this.LoadMusicFiles();
}
/// <summary>Play a song.</summary>
/// <param name="name">The song name to play.</param>
public void PlaySound(string name)
{
// get sound
if (!this.Sounds.TryGetValue(name, out SoundEffectInstance sound))
{
ModCore.ModMonitor.Log("An error occured where we can't find the song anymore. Weird. Please contact Omegasis with a SMAPI Log and describe when/how the event occured.");
return;
}
// play sound
this.lastPlayedSoundName = name;
this.lastPlayedSound = sound;
this.lastPlayedSound.Play();
if (this.playingSounds.ContainsKey(name))
{
this.playingSounds[name].Add(sound);
}
else
{
this.playingSounds.Add(name,new List<SoundEffectInstance>());
this.playingSounds[name].Add(sound);
}
}
/// <summary>Stop the currently playing song.</summary>
public void StopSound()
{
this.lastPlayedSound?.Stop(true);
this.lastPlayedSoundName = null;
}
/// <summary>
/// Stops all of the currently playing sounds.
/// </summary>
public void stopAllSounds()
{
foreach (string soundName in playingSounds.Keys)
{
for (int i = 0; i < playingSounds[soundName].Count; i++)
{
if (playingSounds[soundName][i].State == SoundState.Stopped)
{
playingSounds[soundName][i].Stop(true);
continue;
}
}
}
}
/// <summary>
/// Updates the music pack to clean up all sounds that have been stopped.
/// </summary>
public void update()
{
///Clean up the list.
foreach(string soundName in playingSounds.Keys)
{
for(int i = 0; i < playingSounds[soundName].Count; i++)
{
if(playingSounds[soundName][i].State== SoundState.Stopped)
{
playingSounds[soundName].RemoveAt(i);
i--;
continue;
}
}
}
}
/// <summary>Get whether the content pack is currently playing a sound.</summary>
public bool IsPlaying()
{
return this.lastPlayedSound?.State == SoundState.Playing;
}
/// <summary>
/// Checks if there is a sound with said name playing.
/// </summary>
/// <param name="SoundName"></param>
/// <returns></returns>
public bool IsPlaying(string SoundName)
{
return this.playingSounds[SoundName].FindAll(s => s.State== SoundState.Playing).Count>0;
}
/*********
** Private methods
*********/
/// <summary>Load in the music files from the pack's respective Directory/Songs folder. Typically Content/Music/Wav/FolderName/Songs</summary>
private void LoadMusicFiles()
{
DateTime startTime = DateTime.Now;
DirectoryInfo songFolder = new DirectoryInfo(Path.Combine(this.ContentPack.DirectoryPath, this.MusicFolderName));
foreach (FileInfo file in songFolder.GetFiles())
{
// get name
string name = Path.GetFileNameWithoutExtension(file.Name);
if (this.Sounds.ContainsKey(name))
continue;
// load data
SoundEffect effect = null;
using (Stream waveFileStream = File.OpenRead(file.FullName))
{
switch (file.Extension)
{
case ".wav":
effect = SoundEffect.FromStream(waveFileStream);
break;
case ".mp3":
using (Mp3FileReader reader = new Mp3FileReader(waveFileStream))
using (WaveStream pcmStream = WaveFormatConversionStream.CreatePcmStream(reader))
{
string tempPath = Path.Combine(songFolder.FullName, $"{name}.wav");
ModCore.ModMonitor.Log($"Converting: {tempPath}");
WaveFileWriter.CreateWaveFile(tempPath, pcmStream);
using (Stream tempStream = File.OpenRead(tempPath))
effect = SoundEffect.FromStream(tempStream);
File.Delete(tempPath);
}
break;
case ".ogg":
// Credits: https://social.msdn.microsoft.com/Forums/vstudio/en-US/100a97af-2a1c-4b28-b464-d43611b9b5d6/converting-multichannel-ogg-to-stereo-wav-file?forum=csharpgeneral
using (VorbisWaveReader vorbisStream = new VorbisWaveReader(file.FullName))
{
string tempPath = Path.Combine(songFolder.FullName, $"{name}.wav");
ModCore.DebugLog($"Converting: {tempPath}");
WaveFileWriter.CreateWaveFile(tempPath, vorbisStream.ToWaveProvider16());
using (Stream tempStream = File.OpenRead(tempPath))
effect = SoundEffect.FromStream(tempStream);
File.Delete(tempPath);
}
break;
default:
ModCore.ModMonitor.Log($"Unsupported file extension {file.Extension}.", LogLevel.Warn);
break;
}
}
if (effect == null)
continue;
// add sound
SoundEffectInstance instance = effect.CreateInstance();
this.Sounds.Add(name, instance);
}
// log loading time
if (ModCore.Config.EnableDebugLog)
ModCore.ModMonitor.Log($"Time to load WAV music pack {this.Name}: {startTime.Subtract(DateTime.Now)}");
}
}
}

View File

@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Linq;
namespace SimpleSoundManager.Framework
{
/// <summary>A class that keeps track of the trigger and the list of songs associated with that trigger.</summary>
internal class SongListNode
{
/*********
** Accessors
*********/
/// <summary>The trigger name for the list of songs.</summary>
public string Trigger { get; }
/// <summary>The list of songs associated with a trigger.</summary>
public string[] SongList { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="trigger">The trigger name for the list of songs.</param>
/// <param name="songList">The list of songs associated with a trigger.</param>
public SongListNode(string trigger, IEnumerable<string> songList)
{
this.Trigger = trigger;
this.SongList = songList.ToArray();
}
}
}

View File

@ -1,27 +0,0 @@
namespace SimpleSoundManager.Framework
{
/// <summary>Interface used for common sound functionality;</summary>
public interface Sound
{
/// <summary>Handles playing a sound.</summary>
void play();
void play(float volume);
/// <summary>Handles pausing a song.</summary>
void pause();
/// <summary>Handles stopping a song.</summary>
void stop();
/// <summary>Handles restarting a song.</summary>
void restart();
/// <summary>Handles getting a clone of the song.</summary>
Sound clone();
string getSoundName();
bool isStopped();
}
}

View File

@ -1,202 +0,0 @@
using System.Collections.Generic;
using Microsoft.Xna.Framework.Audio;
using StardewModdingAPI;
using StardewValley;
namespace SimpleSoundManager.Framework
{
public class SoundManager
{
public Dictionary<string, Sound> sounds;
public Dictionary<string, XACTMusicPair> musicBanks;
public float volume;
public List<Sound> currentlyPlayingSounds = new List<Sound>();
/// <summary>Constructor for this class.</summary>
public SoundManager()
{
this.sounds = new Dictionary<string, Sound>();
this.musicBanks = new Dictionary<string, XACTMusicPair>();
this.currentlyPlayingSounds = new List<Sound>();
this.volume = 1.0f;
}
/// <summary>Constructor for wav files.</summary>
public void loadWavFile(string soundName, string pathToWav)
{
WavSound wav = new WavSound(soundName, pathToWav);
SimpleSoundManagerMod.ModMonitor.Log("Getting sound file:" + soundName);
try
{
this.sounds.Add(soundName, wav);
}
catch { }
}
/// <summary>Constructor for wav files.</summary>
public void loadWavFile(IModHelper helper, string soundName, string relativePath)
{
WavSound wav = new WavSound(helper, soundName, relativePath);
SimpleSoundManagerMod.ModMonitor.Log("Getting sound file:" + soundName);
try
{
this.sounds.Add(soundName, wav);
}
catch
{
//Sound already added so no need to worry?
}
}
/// <summary>Constructor for wav files.</summary>
public void loadWavFile(IModHelper helper, string songName, List<string> pathToWav)
{
WavSound wav = new WavSound(helper, songName, pathToWav);
SimpleSoundManagerMod.ModMonitor.Log("Getting sound file:" + songName);
try
{
this.sounds.Add(songName, wav);
}
catch { }
}
/// <summary>Constructor for XACT files.</summary>
public void loadXACTFile(WaveBank waveBank, ISoundBank soundBank, string songName)
{
XACTSound xactSound = new XACTSound(waveBank, soundBank, songName);
this.sounds.Add(songName, xactSound);
}
/// <summary>Constructor for XACT files based on already added music packs.</summary>
public void loadXACTFile(string pairName, string songName)
{
XACTMusicPair musicPair = this.getMusicPack(pairName);
if (pairName == null)
return;
this.loadXACTFile(musicPair.waveBank, musicPair.soundBank, songName);
}
/// <summary>Creates a music pack pair that holds .xwb and .xsb music files.</summary>
/// <param name="helper">The mod's helper that will handle the path of the files.</param>
/// <param name="pairName">The name of this music pack pair.</param>
/// <param name="wavName">The relative path to the .xwb file</param>
/// <param name="soundName">The relative path to the .xsb file</param>
public void loadXACTMusicBank(IModHelper helper, string pairName, string wavName, string soundName)
{
this.musicBanks.Add(pairName, new XACTMusicPair(helper, wavName, soundName));
}
/// <summary>Gets the music pack pair from the sound pool.</summary>
public XACTMusicPair getMusicPack(string name)
{
foreach (var pack in this.musicBanks)
{
if (name == pack.Key)
return pack.Value;
}
return null;
}
/// <summary>Gets a clone of the loaded sound.</summary>
public Sound getSoundClone(string name)
{
foreach (var sound in this.sounds)
{
if (sound.Key == name)
return sound.Value.clone();
}
return null;
}
/// <summary>Play the sound with the given name.</summary>
public void playSound(string soundName)
{
SimpleSoundManagerMod.ModMonitor.Log("Trying to play sound: " + soundName);
foreach (var sound in this.sounds)
{
if (sound.Key == soundName)
{
SimpleSoundManagerMod.ModMonitor.Log("Time to play sound: " + soundName);
var s = this.getSoundClone(soundName);
s.play(this.volume);
this.currentlyPlayingSounds.Add(s);
break;
}
}
}
/// <summary>Play the sound with the given name and volume.</summary>
public void playSound(string soundName, float volume = 1.0f)
{
SimpleSoundManagerMod.ModMonitor.Log("Trying to play sound: " + soundName);
foreach (var sound in this.sounds)
{
if (sound.Key == soundName)
{
SimpleSoundManagerMod.ModMonitor.Log("Time to play sound: " + soundName);
var s = this.getSoundClone(soundName);
s.play(volume);
this.currentlyPlayingSounds.Add(s);
break;
}
}
}
/// <summary>Stop the sound that is playing.</summary>
public void stopSound(string soundName)
{
List<Sound> removalList = new List<Sound>();
foreach (var sound in this.currentlyPlayingSounds)
{
if (sound.getSoundName() == soundName)
{
sound.stop();
removalList.Add(sound);
}
}
foreach (var v in removalList)
this.currentlyPlayingSounds.Remove(v);
}
/// <summary>Pause the sound with this name?</summary>
public void pauseSound(string soundName)
{
List<Sound> removalList = new List<Sound>();
foreach (var sound in this.currentlyPlayingSounds)
{
if (sound.getSoundName() == soundName)
{
sound.pause();
removalList.Add(sound);
}
}
foreach (var v in removalList)
this.currentlyPlayingSounds.Remove(v);
}
public void swapSounds(string newSong)
{
this.playSound(newSong);
}
public void update()
{
List<Sound> removalList = new List<Sound>();
foreach (Sound song in this.currentlyPlayingSounds)
{
if (song.isStopped())
removalList.Add(song);
}
foreach (var v in removalList)
this.currentlyPlayingSounds.Remove(v);
}
public void stopAllSounds()
{
foreach (var v in this.currentlyPlayingSounds)
v.stop();
}
}
}

View File

@ -1,211 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework.Audio;
using SimpleSoundManager.Framework;
using StardewModdingAPI;
namespace SimpleSoundManager
{
class WavSound : Sound
{
/// <summary>Used to actually play the song.</summary>
DynamicSoundEffectInstance dynamicSound;
/// <summary>Used to keep track of where in the song we are.</summary>
int position;
int count;
/// <summary>Used to store the info for the song.</summary>
byte[] byteArray;
public string path;
public string soundName;
public bool loop;
/// <summary>Get a raw disk path to the wav file.</summary>
public WavSound(string name, string pathToWavFile, bool loop = false)
{
this.path = pathToWavFile;
this.LoadWavFromFileToStream();
this.soundName = name;
this.loop = loop;
}
/// <summary>A constructor that takes a mod helper and a relative path to a wav file.</summary>
public WavSound(IModHelper modHelper, string name, string relativePath, bool loop = false)
{
string path = Path.Combine(modHelper.DirectoryPath, relativePath);
this.path = path;
this.soundName = name;
this.loop = loop;
}
/// <summary>Constructor that is more flexible than typing an absolute path.</summary>
/// <param name="modHelper">The mod helper for the mod you wish to use to load the music files from.</param>
/// <param name="pathPieces">The list of folders and files that make up a complete path.</param>
public WavSound(IModHelper modHelper, string soundName, List<string> pathPieces, bool loop = false)
{
string dirPath = modHelper.DirectoryPath;
foreach (string str in pathPieces)
dirPath = Path.Combine(dirPath, str);
this.path = dirPath;
this.soundName = soundName;
this.loop = loop;
}
/// <summary>Loads the .wav file from disk and plays it.</summary>
public void LoadWavFromFileToStream()
{
// Create a new SpriteBatch, which can be used to draw textures.
string file = this.path;
Stream waveFileStream = File.OpenRead(file); //TitleContainer.OpenStream(file);
BinaryReader reader = new BinaryReader(waveFileStream);
int chunkID = reader.ReadInt32();
int fileSize = reader.ReadInt32();
int riffType = reader.ReadInt32();
int fmtID = reader.ReadInt32();
int fmtSize = reader.ReadInt32();
int fmtCode = reader.ReadInt16();
int channels = reader.ReadInt16();
int sampleRate = reader.ReadInt32();
int fmtAvgBPS = reader.ReadInt32();
int fmtBlockAlign = reader.ReadInt16();
int bitDepth = reader.ReadInt16();
if (fmtSize == 18)
{
// Read any extra values
int fmtExtraSize = reader.ReadInt16();
reader.ReadBytes(fmtExtraSize);
}
int dataID = reader.ReadInt32();
int dataSize = reader.ReadInt32();
this.byteArray = reader.ReadBytes(dataSize);
this.dynamicSound = new DynamicSoundEffectInstance(sampleRate, (AudioChannels)channels);
this.count = this.byteArray.Length;//dynamicSound.GetSampleSizeInBytes(TimeSpan.FromMilliseconds(1000));
this.dynamicSound.BufferNeeded += this.DynamicSound_BufferNeeded;
}
void DynamicSound_BufferNeeded(object sender, EventArgs e)
{
try
{
this.dynamicSound.SubmitBuffer(this.byteArray, this.position, this.count);
}
catch { }
this.position += this.count;
if (this.position + this.count > this.byteArray.Length)
{
if (this.loop)
this.position = 0;
//else
// this.stop();
}
}
/// <summary>Used to pause the current song.</summary>
public void pause()
{
this.dynamicSound?.Pause();
}
/// <summary>Used to play a song.</summary>
public void play()
{
if (this.isPlaying())
return;
this.LoadWavFromFileToStream();
this.dynamicSound.Play();
}
/// <summary>Used to play a song.</summary>
/// <param name="volume">How lound the sound is when playing. 0~1.0f</param>
public void play(float volume)
{
if (this.isPlaying())
return;
this.LoadWavFromFileToStream();
this.dynamicSound.Volume = volume;
this.dynamicSound.Play();
}
/// <summary>Used to resume the currently playing song.</summary>
public void resume()
{
this.dynamicSound?.Resume();
}
/// <summary>Used to stop the currently playing song.</summary>
public void stop()
{
if (this.dynamicSound != null)
{
this.dynamicSound.Stop(true);
this.dynamicSound.BufferNeeded -= this.DynamicSound_BufferNeeded;
this.position = 0;
this.count = 0;
this.byteArray = new byte[0];
}
}
/// <summary>Used to change from one playing song to another;</summary>
public void swap(string pathToNewWavFile)
{
this.stop();
this.path = pathToNewWavFile;
this.play();
}
/// <summary>Checks if the song is currently playing.</summary>
public bool isPlaying()
{
return this.dynamicSound?.State == SoundState.Playing;
}
/// <summary>Checks if the song is currently paused.</summary>
public bool isPaused()
{
return this.dynamicSound?.State == SoundState.Paused;
}
/// <summary>Checks if the song is currently stopped.</summary>
public bool isStopped()
{
return this.dynamicSound?.State == SoundState.Stopped;
}
public Sound clone()
{
return new WavSound(this.getSoundName(), this.path);
}
public string getSoundName()
{
return this.soundName;
}
public void restart()
{
this.stop();
this.play();
}
}
}

View File

@ -1,118 +0,0 @@
using Microsoft.Xna.Framework.Audio;
using StardewValley;
namespace SimpleSoundManager.Framework
{
public class XACTSound : Sound
{
public WaveBank waveBank;
public ISoundBank soundBank;
public string soundName;
readonly WaveBank vanillaWaveBank;
readonly ISoundBank vanillaSoundBank;
readonly Cue song;
/// <summary>Make a new Sound Manager to play and manage sounds in a modded wave bank.</summary>
/// <param name="newWaveBank">The reference to the wave bank in the mod's asset folder.</param>
/// <param name="newSoundBank">The reference to the sound bank in the mod's asset folder.</param>
public XACTSound(WaveBank newWaveBank, ISoundBank newSoundBank, string soundName)
{
this.waveBank = newWaveBank;
this.soundBank = newSoundBank;
this.vanillaSoundBank = Game1.soundBank;
this.vanillaWaveBank = Game1.waveBank;
this.soundName = soundName;
this.song = this.soundBank.GetCue(this.soundName);
}
/// <summary>Play a sound from the mod's wave bank.</summary>
/// <param name="soundName">The name of the sound in the mod's wave bank. This will fail if the sound doesn't exists. This is also case sensitive.</param>
public void play(string soundName)
{
Game1.waveBank = this.waveBank;
Game1.soundBank = this.soundBank;
if (this.song == null) return;
this.song.Play();
Game1.waveBank = this.vanillaWaveBank;
Game1.soundBank = this.vanillaSoundBank;
}
/// <summary>Pauses the first instance of this sound.</summary>
/// <param name="soundName"></param>
public void pause(string soundName)
{
this.song?.Pause();
}
/// <summary>Resume the first instance of the sound that has this name.</summary>
public void resume(string soundName)
{
this.song?.Resume();
}
/// <summary>Stop the first instance of the sound that has this name.</summary>
/// <param name="soundName"></param>
public void stop(string soundName)
{
this.song?.Stop(AudioStopOptions.Immediate);
}
/// <summary>Resumes a paused song.</summary>
public void resume()
{
this.resume(this.soundName);
}
/// <summary>Plays this song.</summary>
public void play()
{
this.play(this.soundName);
}
/// <summary>Plays this song.</summary>
public void play(float volume)
{
this.play(this.soundName);
}
/// <summary>Pauses this song.</summary>
public void pause()
{
this.pause(this.soundName);
}
/// <summary>Stops this somg.</summary>
public void stop()
{
this.stop(this.soundName);
}
/// <summary>Restarts this song.</summary>
public void restart()
{
this.stop();
this.play();
}
/// <summary>Gets a clone of this song.</summary>
public Sound clone()
{
return new XACTSound(this.waveBank, this.soundBank, this.soundName);
}
public string getSoundName()
{
return this.soundName;
}
public bool isStopped()
{
return this.song == null || this.song.IsStopped;
}
}
}

View File

@ -1,26 +0,0 @@
using System.IO;
using Microsoft.Xna.Framework.Audio;
using StardewModdingAPI;
using StardewValley;
namespace SimpleSoundManager
{
public class XACTMusicPair
{
public WaveBank waveBank;
public ISoundBank soundBank;
/// <summary>Create a xwb and xsb music pack pair.</summary>
/// <param name="helper">The mod helper from the mod that will handle loading in the file.</param>
/// <param name="wavBankPath">A relative path to the .xwb file in the mod helper's mod directory.</param>
/// <param name="soundBankPath">A relative path to the .xsb file in the mod helper's mod directory.</param>
public XACTMusicPair(IModHelper helper, string wavBankPath, string soundBankPath)
{
wavBankPath = Path.Combine(helper.DirectoryPath, wavBankPath);
soundBankPath = Path.Combine(helper.DirectoryPath, soundBankPath);
this.waveBank = new WaveBank(Game1.audioEngine, wavBankPath);
this.soundBank = new SoundBankWrapper(new SoundBank(Game1.audioEngine, soundBankPath));
}
}
}

View File

@ -0,0 +1,80 @@
using SimpleSoundManager.Framework;
using SimpleSoundManager.Framework.Music;
using StardewModdingAPI;
namespace SimpleSoundManager
{
/// <summary>
/// Mod core.
///
/// Needs testing.
///
/// Seems like the current structure will require both content packs and a programmed mod to request when to play specific sounds. Interesting.
/// </summary>
public class ModCore : Mod
{
internal static IModHelper ModHelper;
internal static IMonitor ModMonitor;
internal static Config Config;
public static MusicManager MusicManager;
/// <summary>The mod entry point, called after the mod is first loaded.</summary>
/// <param name="helper">Provides simplified APIs for writing mods.</param>
public override void Entry(IModHelper helper)
{
ModHelper = helper;
ModMonitor = this.Monitor;
Config = helper.ReadConfig<Config>();
this.loadContentPacks();
this.Helper.Events.GameLoop.OneSecondUpdateTicked += this.GameLoop_OneSecondUpdateTicked;
this.Helper.Events.GameLoop.SaveLoaded += this.GameLoop_SaveLoaded;
}
private void GameLoop_SaveLoaded(object sender, StardewModdingAPI.Events.SaveLoadedEventArgs e)
{
//MusicManager.MusicPacks["Your Project Name"].PlaySound("toby fox - UNDERTALE Soundtrack - 01 Once Upon a Time");
//DebugLog("PLAY SOME SOUNDS");
}
/// <summary>
/// Update all music packs every second.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void GameLoop_OneSecondUpdateTicked(object sender, StardewModdingAPI.Events.OneSecondUpdateTickedEventArgs e)
{
foreach(MusicPack pack in MusicManager.MusicPacks.Values)
{
pack.update();
}
}
/// <summary>
/// Loads all content packs for SimpleSoundManager
/// </summary>
private void loadContentPacks()
{
MusicManager = new MusicManager();
foreach (IContentPack contentPack in this.Helper.ContentPacks.GetOwned())
{
this.Monitor.Log($"Reading content pack: {contentPack.Manifest.Name} {contentPack.Manifest.Version} from {contentPack.DirectoryPath}");
MusicPack musicPack = new MusicPack(contentPack);
MusicManager.addMusicPack(musicPack, true, true);
}
}
/// <summary>
/// Easy way to display debug logs when allowing for a check to see if they are enabled.
/// </summary>
/// <param name="s">The message to display.</param>
public static void DebugLog(string s)
{
if (Config.EnableDebugLog)
ModMonitor.Log(s);
}
}
}

View File

@ -66,6 +66,12 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NAudio">
<Version>1.8.5</Version>
</PackageReference>
<PackageReference Include="NAudio.Vorbis">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Pathoschild.Stardew.ModBuildConfig" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
@ -73,12 +79,11 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Framework\Sound.cs" />
<Compile Include="Framework\SoundManager.cs" />
<Compile Include="Framework\WavSound.cs" />
<Compile Include="Framework\XactMusicPair.cs" />
<Compile Include="Framework\XACTSound.cs" />
<Compile Include="SimpleSoundManagerMod.cs" />
<Compile Include="Framework\Music\Config.cs" />
<Compile Include="Framework\Music\MusicManager.cs" />
<Compile Include="Framework\Music\MusicPack.cs" />
<Compile Include="Framework\Music\SongListNode.cs" />
<Compile Include="ModCore.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -1,18 +0,0 @@
using StardewModdingAPI;
namespace SimpleSoundManager
{
public class SimpleSoundManagerMod : Mod
{
internal static IModHelper ModHelper;
internal static IMonitor ModMonitor;
/// <summary>The mod entry point, called after the mod is first loaded.</summary>
/// <param name="helper">Provides simplified APIs for writing mods.</param>
public override void Entry(IModHelper helper)
{
ModHelper = helper;
ModMonitor = this.Monitor;
}
}
}

View File

@ -1,216 +0,0 @@
0000-54e0: ff 1b 62 00-00 ff ff 25-62 00 00 ff-ff 2e 62 00 ..b....% b.....b.
0000-54f0: 00 ff ff 33-62 00 00 ff-ff 41 62 00-00 ff ff 64 ...3b... .Ab....d
0000-5500: 6f 6f 72 43-6c 6f 73 65-00 61 72 63-68 61 65 6f oorClose .archaeo
0000-5510: 00 61 78 65-00 62 6f 75-6c 64 65 72-43 72 61 63 .axe.bou lderCrac
0000-5520: 6b 00 63 6f-69 6e 00 63-75 74 00 64-69 61 6c 6f k.coin.c ut.dialo
0000-5530: 67 75 65 43-68 61 72 61-63 74 65 72-00 64 69 61 gueChara cter.dia
0000-5540: 6c 6f 67 75-65 43 68 61-72 61 63 74-65 72 43 6c logueCha racterCl
0000-5550: 6f 73 65 00-64 72 6f 70-49 74 65 6d-49 6e 57 61 ose.drop ItemInWa
0000-5560: 74 65 72 00-67 68 6f 73-74 00 67 72-75 6e 74 00 ter.ghos t.grunt.
0000-5570: 68 61 6d 6d-65 72 00 68-6f 65 48 69-74 00 6f 70 hammer.h oeHit.op
0000-5580: 65 6e 42 6f-78 00 70 69-63 6b 55 70-49 74 65 6d enBox.pi ckUpItem
0000-5590: 00 73 61 6e-64 79 53 74-65 70 00 73-65 65 64 73 .sandySt ep.seeds
0000-55a0: 00 73 68 61-64 6f 77 70-65 65 70 00-73 68 69 6e .shadowp eep.shin
0000-55b0: 79 34 00 73-6d 61 6c 6c-53 65 6c 65-63 74 00 74 y4.small Select.t
0000-55c0: 68 72 6f 77-44 6f 77 6e-49 54 65 6d-00 67 72 61 hrowDown ITem.gra
0000-55d0: 73 73 79 53-74 65 70 00-73 74 6f 6e-65 53 74 65 ssyStep. stoneSte
0000-55e0: 70 00 74 68-75 64 53 74-65 70 00 65-61 74 00 66 p.thudSt ep.eat.f
0000-55f0: 69 73 68 42-69 74 65 00-74 6f 6f 6c-53 77 61 70 ishBite. toolSwap
0000-5600: 00 70 75 6c-6c 49 74 65-6d 46 72 6f-6d 57 61 74 .pullIte mFromWat
0000-5610: 65 72 00 77-6f 6f 64 79-53 74 65 70-00 62 6f 62 er.woody Step.bob
0000-5620: 00 64 77 6f-6f 70 00 65-78 70 6c 6f-73 69 6f 6e .dwoop.e xplosion
0000-5630: 00 63 72 61-66 74 69 6e-67 00 64 65-74 65 63 74 .craftin g.detect
0000-5640: 6f 72 00 66-75 72 6e 61-63 65 00 70-6f 77 65 72 or.furna ce.power
0000-5650: 75 70 00 64-65 73 6f 6c-61 74 65 00-6a 61 75 6e up.desol ate.jaun
0000-5660: 74 79 00 73-74 61 72 73-68 6f 6f 74-00 73 61 70 ty.stars hoot.sap
0000-5670: 70 79 70 69-61 6e 6f 00-6d 75 73 69-63 62 6f 78 pypiano. musicbox
0000-5680: 73 6f 6e 67-00 35 30 73-00 72 61 67-74 69 6d 65 song.50s .ragtime
0000-5690: 00 73 61 64-70 69 61 6e-6f 00 66 75-73 65 00 65 .sadpian o.fuse.e
0000-56a0: 63 68 6f 73-00 73 61 6d-70 72 61 63-74 69 63 65 chos.sam practice
0000-56b0: 00 68 65 61-76 79 00 68-6f 6e 6b 79-74 6f 6e 6b .heavy.h onkytonk
0000-56c0: 79 00 70 6f-70 70 79 00-73 68 69 6d-6d 65 72 69 y.poppy. shimmeri
0000-56d0: 6e 67 62 61-73 74 69 6f-6e 00 61 65-72 6f 62 69 ngbastio n.aerobi
0000-56e0: 63 73 00 68-69 74 45 6e-65 6d 79 00-73 6c 69 6d cs.hitEn emy.slim
0000-56f0: 65 00 44 75-67 67 79 00-6d 6f 6e 65-79 00 74 6f e.Duggy. money.to
0000-5700: 6f 6c 43 68-61 72 67 65-00 6f 77 00-64 65 61 74 olCharge .ow.deat
0000-5710: 68 00 66 69-72 65 62 61-6c 6c 00 72-61 62 62 69 h.fireba ll.rabbi
0000-5720: 74 00 77 6f-6f 64 79 48-69 74 00 62-72 65 61 74 t.woodyH it.breat
0000-5730: 68 6f 75 74-00 62 72 65-61 74 68 69-6e 00 77 69 hout.bre athin.wi
0000-5740: 6e 64 00 73-70 72 69 6e-67 74 6f 77-6e 00 77 61 nd.sprin gtown.wa
0000-5750: 76 79 00 63-6c 75 62 6c-6f 6f 70 00-61 63 68 69 vy.clubl oop.achi
0000-5760: 65 76 65 6d-65 6e 74 00-77 65 64 64-69 6e 67 00 evement. wedding.
0000-5770: 66 6c 75 74-65 00 64 72-75 6d 6b 69-74 30 00 64 flute.dr umkit0.d
0000-5780: 72 75 6d 6b-69 74 31 00-64 72 75 6d-6b 69 74 32 rumkit1. drumkit2
0000-5790: 00 64 72 75-6d 6b 69 74-33 00 64 72-75 6d 6b 69 .drumkit 3.drumki
0000-57a0: 74 34 00 64-72 75 6d 6b-69 74 35 00-64 72 75 6d t4.drumk it5.drum
0000-57b0: 6b 69 74 36-00 70 68 6f-6e 65 00 74-68 75 6e 64 kit6.pho ne.thund
0000-57c0: 65 72 00 72-61 69 6e 00-63 72 69 63-6b 65 74 73 er.rain. crickets
0000-57d0: 00 77 61 6e-64 00 74 69-74 6c 65 5f-6e 69 67 68 .wand.ti tle_nigh
0000-57e0: 74 00 71 75-65 73 74 63-6f 6d 70 6c-65 74 65 00 t.questc omplete.
0000-57f0: 63 61 76 65-64 72 69 70-00 6f 62 6a-65 63 74 69 cavedrip .objecti
0000-5800: 76 65 43 6f-6d 70 6c 65-74 65 00 62-61 63 6b 70 veComple te.backp
0000-5810: 61 63 6b 49-4e 00 43 72-79 73 74 61-6c 20 42 65 ackIN.Cr ystal.Be
0000-5820: 6c 6c 73 00-43 61 76 65-72 6e 00 53-65 63 72 65 lls.Cave rn.Secre
0000-5830: 74 20 47 6e-6f 6d 65 73-00 43 6c 6f-74 68 00 49 t.Gnomes .Cloth.I
0000-5840: 63 69 63 6c-65 73 00 58-4f 52 00 4e-65 61 72 20 cicles.X OR.Near.
0000-5850: 54 68 65 20-50 6c 61 6e-65 74 20 43-6f 72 65 00 The.Plan et.Core.
0000-5860: 4f 66 20 44-77 61 72 76-65 73 00 73-70 72 69 6e Of.Dwarv es.sprin
0000-5870: 67 32 00 73-70 72 69 6e-67 33 00 50-69 6e 6b 20 g2.sprin g3.Pink.
0000-5880: 50 65 74 61-6c 73 00 54-72 6f 70 69-63 61 6c 20 Petals.T ropical.
0000-5890: 4a 61 6d 00-47 68 6f 73-74 20 53 79-6e 74 68 00 Jam.Ghos t.Synth.
0000-58a0: 50 6c 75 6d-73 00 4d 61-6a 65 73 74-69 63 00 4f Plums.Ma jestic.O
0000-58b0: 72 61 6e 67-65 00 73 75-6d 6d 65 72-32 00 43 79 range.su mmer2.Cy
0000-58c0: 63 6c 6f 70-73 00 77 69-6e 74 65 72-33 00 4e 65 clops.wi nter3.Ne
0000-58d0: 77 20 53 6e-6f 77 00 63-72 6f 61 6b-00 74 72 65 w.Snow.c roak.tre
0000-58e0: 65 74 68 75-64 00 74 72-65 65 63 72-61 63 6b 00 ethud.tr eecrack.
0000-58f0: 61 78 63 68-6f 70 00 6c-65 61 66 72-75 73 74 6c axchop.l eafrustl
0000-5900: 65 00 63 72-79 73 74 61-6c 00 73 77-65 65 74 00 e.crysta l.sweet.
0000-5910: 70 6f 74 74-65 72 79 53-6d 61 73 68-00 73 65 6c potteryS mash.sel
0000-5920: 65 63 74 00-66 6c 61 6d-65 53 70 65-6c 6c 48 69 ect.flam eSpellHi
0000-5930: 74 00 66 6c-61 6d 65 53-70 65 6c 6c-00 64 65 62 t.flameS pell.deb
0000-5940: 75 66 66 48-69 74 00 64-65 62 75 66-66 53 70 65 uffHit.d ebuffSpe
0000-5950: 6c 6c 00 6d-6f 6e 73 74-65 72 64 65-61 64 00 63 ll.monst erdead.c
0000-5960: 6c 75 62 68-69 74 00 63-6c 75 62 73-77 69 70 65 lubhit.c lubswipe
0000-5970: 00 6f 70 65-6e 43 68 65-73 74 00 72-6f 63 6b 47 .openChe st.rockG
0000-5980: 6f 6c 65 6d-53 70 61 77-6e 00 72 6f-63 6b 47 6f olemSpaw n.rockGo
0000-5990: 6c 65 6d 44-69 65 00 64-61 72 6b 43-61 76 65 4c lemDie.d arkCaveL
0000-59a0: 6f 6f 70 00-62 75 67 4c-65 76 65 6c-4c 6f 6f 70 oop.bugL evelLoop
0000-59b0: 00 62 61 74-46 6c 61 70-00 62 61 74-53 63 72 65 .batFlap .batScre
0000-59c0: 65 63 68 00-62 69 67 44-72 75 6d 73-00 63 6c 75 ech.bigD rums.clu
0000-59d0: 62 53 6d 61-73 68 00 6f-63 65 61 6e-00 73 70 72 bSmash.o cean.spr
0000-59e0: 69 6e 67 5f-64 61 79 5f-61 6d 62 69-65 6e 74 00 ing_day_ ambient.
0000-59f0: 6d 61 72 6e-69 65 53 68-6f 70 00 74-6f 79 50 69 marnieSh op.toyPi
0000-5a00: 61 6e 6f 00-73 6b 65 6c-65 74 6f 6e-53 74 65 70 ano.skel etonStep
0000-5a10: 00 73 6b 65-6c 65 74 6f-6e 44 69 65-00 73 6b 65 .skeleto nDie.ske
0000-5a20: 6c 65 74 6f-6e 48 69 74-00 73 6c 69-6d 65 48 69 letonHit .slimeHi
0000-5a30: 74 00 64 75-73 74 4d 65-65 70 00 74-68 72 6f 77 t.dustMe ep.throw
0000-5a40: 00 6d 69 6e-65 63 61 72-74 4c 6f 6f-70 00 43 6c .minecar tLoop.Cl
0000-5a50: 6f 75 64 43-6f 75 6e 74-72 79 00 72-6f 61 64 6e oudCount ry.roadn
0000-5a60: 6f 69 73 65-00 62 75 73-44 6f 6f 72-4f 70 65 6e oise.bus DoorOpen
0000-5a70: 00 53 65 74-74 6c 69 6e-67 49 6e 00-73 69 70 54 .Settlin gIn.sipT
0000-5a80: 65 61 00 73-68 61 64 6f-77 44 69 65-00 73 68 61 ea.shado wDie.sha
0000-5a90: 64 6f 77 48-69 74 00 68-65 61 6c 53-6f 75 6e 64 dowHit.h ealSound
0000-5aa0: 00 63 6f 6c-64 53 70 65-6c 6c 00 74-72 69 62 61 .coldSpe ll.triba
0000-5ab0: 6c 00 55 70-70 65 72 5f-41 6d 62 69-65 6e 74 00 l.Upper_ Ambient.
0000-5ac0: 46 72 6f 73-74 5f 41 6d-62 69 65 6e-74 00 4c 61 Frost_Am bient.La
0000-5ad0: 76 61 5f 41-6d 62 69 65-6e 74 00 79-6f 62 61 00 va_Ambie nt.yoba.
0000-5ae0: 77 61 72 72-69 6f 72 00-73 6c 69 6e-67 73 68 6f warrior. slingsho
0000-5af0: 74 00 64 69-73 63 6f 76-65 72 4d 69-6e 65 72 61 t.discov erMinera
0000-5b00: 6c 00 74 72-61 73 68 63-61 6e 00 6e-65 77 41 72 l.trashc an.newAr
0000-5b10: 74 69 66 61-63 74 00 72-65 77 61 72-64 00 6e 65 tifact.r eward.ne
0000-5b20: 77 52 65 63-6f 72 64 00-6e 65 77 52-65 63 69 70 wRecord. newRecip
0000-5b30: 65 00 4f 76-65 72 63 61-73 74 00 77-6f 6f 64 73 e.Overca st.woods
0000-5b40: 54 68 65 6d-65 00 73 74-75 6d 70 43-72 61 63 6b Theme.st umpCrack
0000-5b50: 00 73 65 63-72 65 74 31-00 74 72 61-69 6e 57 68 .secret1 .trainWh
0000-5b60: 69 73 74 6c-65 00 74 72-61 69 6e 4c-6f 6f 70 00 istle.tr ainLoop.
0000-5b70: 67 65 74 4e-65 77 53 70-65 63 69 61-6c 49 74 65 getNewSp ecialIte
0000-5b80: 6d 00 6e 69-67 68 74 54-69 6d 65 00-4d 65 74 65 m.nightT ime.Mete
0000-5b90: 6f 72 69 74-65 00 55 46-4f 00 6f 77-6c 00 64 6f orite.UF O.owl.do
0000-5ba0: 67 73 00 73-63 69 73 73-6f 72 73 00-4d 69 6c 6b gs.sciss ors.Milk
0000-5bb0: 69 6e 67 00-44 75 63 6b-00 73 68 65-65 70 00 6b ing.Duck .sheep.k
0000-5bc0: 69 6c 6c 41-6e 69 6d 61-6c 00 62 6f-75 6c 64 65 illAnima l.boulde
0000-5bd0: 72 42 72 65-61 6b 00 73-65 6c 6c 00-70 75 72 63 rBreak.s ell.purc
0000-5be0: 68 61 73 65-43 6c 69 63-6b 00 63 61-73 74 00 53 haseClic k.cast.S
0000-5bf0: 69 6e 57 61-76 65 00 73-6c 6f 77 52-65 65 6c 00 inWave.s lowReel.
0000-5c00: 66 61 73 74-52 65 65 6c-00 74 69 6e-79 57 68 69 fastReel .tinyWhi
0000-5c10: 70 00 62 75-74 74 6f 6e-31 00 46 69-73 68 48 69 p.button 1.FishHi
0000-5c20: 74 00 6a 69-6e 67 6c 65-31 00 66 69-73 68 45 73 t.jingle 1.fishEs
0000-5c30: 63 61 70 65-00 66 69 73-68 53 6c 61-70 00 43 6f cape.fis hSlap.Co
0000-5c40: 77 62 6f 79-5f 4f 56 45-52 57 4f 52-4c 44 00 43 wboy_OVE RWORLD.C
0000-5c50: 6f 77 62 6f-79 5f 73 69-6e 67 69 6e-67 00 43 6f owboy_si nging.Co
0000-5c60: 77 62 6f 79-5f 53 65 63-72 65 74 00-43 6f 77 62 wboy_Sec ret.Cowb
0000-5c70: 6f 79 5f 67-75 6e 73 68-6f 74 00 63-6f 77 62 6f oy_gunsh ot.cowbo
0000-5c80: 79 5f 62 6f-73 73 00 63-6f 77 62 6f-79 5f 64 65 y_boss.c owboy_de
0000-5c90: 61 64 00 43-6f 77 62 6f-79 5f 46 6f-6f 74 73 74 ad.Cowbo y_Footst
0000-5ca0: 65 70 00 43-6f 77 62 6f-79 5f 75 6e-64 65 61 64 ep.Cowbo y_undead
0000-5cb0: 00 63 6f 77-62 6f 79 5f-70 6f 77 65-72 75 70 00 .cowboy_ powerup.
0000-5cc0: 63 6f 77 62-6f 79 5f 67-75 6e 6c 6f-61 64 00 63 cowboy_g unload.c
0000-5cd0: 6f 77 62 6f-79 5f 67 6f-70 68 65 72-00 63 6f 77 owboy_go pher.cow
0000-5ce0: 62 6f 79 5f-65 78 70 6c-6f 73 69 6f-6e 00 63 6f boy_expl osion.co
0000-5cf0: 77 62 6f 79-5f 6f 75 74-6c 61 77 73-6f 6e 67 00 wboy_out lawsong.
0000-5d00: 70 6c 61 79-66 75 6c 00-41 62 69 67-61 69 6c 46 playful. AbigailF
0000-5d10: 6c 75 74 65-00 41 62 69-67 61 69 6c-46 6c 75 74 lute.Abi gailFlut
0000-5d20: 65 44 75 65-74 00 62 72-65 65 7a 79-00 6b 69 6e eDuet.br eezy.kin
0000-5d30: 64 61 64 75-6d 62 61 75-74 75 6d 6e-00 48 6f 73 dadumbau tumn.Hos
0000-5d40: 70 69 74 61-6c 5f 41 6d-62 69 65 6e-74 00 62 72 pital_Am bient.br
0000-5d50: 65 61 6b 69-6e 67 47 6c-61 73 73 00-73 70 61 63 eakingGl ass.spac
0000-5d60: 65 4d 75 73-69 63 00 72-6f 62 6f 74-53 6f 75 6e eMusic.r obotSoun
0000-5d70: 64 45 66 66-65 63 74 73-00 72 6f 62-6f 74 42 4c dEffects .robotBL
0000-5d80: 41 53 54 4f-46 46 00 70-6f 6f 6c 5f-61 6d 62 69 ASTOFF.p ool_ambi
0000-5d90: 65 6e 74 00-63 61 6d 65-72 61 4e 6f-69 73 65 00 ent.came raNoise.
0000-5da0: 6b 65 79 62-6f 61 72 64-54 79 70 69-6e 67 00 6d keyboard Typing.m
0000-5db0: 6f 75 73 65-43 6c 69 63-6b 00 65 6c-6c 69 6f 74 ouseClic k.elliot
0000-5dc0: 74 50 69 61-6e 6f 00 74-69 6e 79 6d-75 73 69 63 tPiano.t inymusic
0000-5dd0: 62 6f 78 00-67 75 73 76-69 6f 6c 69-6e 00 77 68 box.gusv iolin.wh
0000-5de0: 69 73 74 6c-65 00 65 76-65 6e 74 31-00 74 69 63 istle.ev ent1.tic
0000-5df0: 6b 54 6f 63-6b 00 46 6c-6f 77 65 72-44 61 6e 63 kTock.Fl owerDanc
0000-5e00: 65 00 65 76-65 6e 74 32-00 6d 6f 6f-6e 6c 69 67 e.event2 .moonlig
0000-5e10: 68 74 4a 65-6c 6c 69 65-73 00 66 61-6c 6c 46 65 htJellie s.fallFe
0000-5e20: 73 74 00 63-68 72 69 73-74 6d 61 73-54 68 65 6d st.chris tmasThem
0000-5e30: 65 00 6a 75-6e 69 6d 6f-4d 65 65 70-31 00 63 6f e.junimo Meep1.co
0000-5e40: 6d 6d 75 6e-69 74 79 43-65 6e 74 65-72 00 6a 75 mmunityC enter.ju
0000-5e50: 6e 69 6d 6f-53 74 61 72-53 6f 6e 67-00 62 75 73 nimoStar Song.bus
0000-5e60: 44 72 69 76-65 4f 66 66-00 73 74 61-69 72 73 64 DriveOff .stairsd
0000-5e70: 6f 77 6e 00-73 65 72 70-65 6e 74 48-69 74 00 73 own.serp entHit.s
0000-5e80: 65 72 70 65-6e 74 44 69-65 00 64 6f-67 57 68 69 erpentDi e.dogWhi
0000-5e90: 6e 69 6e 67-00 73 68 77-69 70 00 66-61 6c 6c 44 ning.shw ip.fallD
0000-5ea0: 6f 77 6e 00-57 69 7a 61-72 64 53 6f-6e 67 00 64 own.Wiza rdSong.d
0000-5eb0: 6f 6f 72 43-72 65 61 6b-52 65 76 65-72 73 65 00 oorCreak Reverse.
0000-5ec0: 63 61 63 6b-6c 69 6e 67-57 69 74 63-68 00 67 6c cackling Witch.gl
0000-5ed0: 75 67 00 68-61 72 76 65-73 74 00 72-6f 6f 73 74 ug.harve st.roost
0000-5ee0: 65 72 00 64-6f 67 5f 70-61 6e 74 00-64 6f 67 5f er.dog_p ant.dog_
0000-5ef0: 62 61 72 6b-00 73 70 69-72 69 74 73-5f 65 76 65 bark.spi rits_eve
0000-5f00: 00 4d 61 69-6e 54 68 65-6d 65 00 6a-6f 6a 61 4f .MainThe me.jojaO
0000-5f10: 66 66 69 63-65 53 6f 75-6e 64 73 63-61 70 65 00 fficeSou ndscape.
0000-5f20: 67 72 61 6e-64 70 61 73-5f 74 68 65-6d 65 00 66 grandpas _theme.f
0000-5f30: 61 6c 6c 5f-64 61 79 5f-61 6d 62 69-65 6e 74 00 all_day_ ambient.
0000-5f40: 73 75 6d 6d-65 72 5f 64-61 79 5f 61-6d 62 69 65 summer_d ay_ambie
0000-5f50: 6e 74 00 73-6e 6f 77 79-53 74 65 70-00 73 70 72 nt.snowy Step.spr
0000-5f60: 69 6e 67 31-00 73 75 6d-6d 65 72 31-00 73 75 6d ing1.sum mer1.sum
0000-5f70: 6d 65 72 33-00 66 61 6c-6c 33 00 66-61 6c 6c 31 mer3.fal l3.fall1
0000-5f80: 00 66 61 6c-6c 32 00 77-69 6e 74 65-72 32 00 77 .fall2.w inter2.w
0000-5f90: 69 6e 74 65-72 31 00 6c-69 62 72 61-72 79 54 68 inter1.l ibraryTh
0000-5fa0: 65 6d 65 00-63 72 61 63-6b 6c 69 6e-67 46 69 72 eme.crac klingFir
0000-5fb0: 65 00 62 61-62 62 6c 69-6e 67 42 72-6f 6f 6b 00 e.babbli ngBrook.
0000-5fc0: 68 65 61 76-79 45 6e 67-69 6e 65 00-73 70 72 69 heavyEng ine.spri
0000-5fd0: 6e 67 5f 6e-69 67 68 74-5f 61 6d 62-69 65 6e 74 ng_night _ambient
0000-5fe0: 00 63 72 69-63 6b 65 74-73 41 6d 62-69 65 6e 74 .cricket sAmbient
0000-5ff0: 00 64 69 73-74 61 6e 74-42 61 6e 6a-6f 00 67 69 .distant Banjo.gi
0000-6000: 76 65 5f 67-69 66 74 00-4d 61 72 6c-6f 6e 73 54 ve_gift. MarlonsT
0000-6010: 68 65 6d 65-00 53 61 6c-6f 6f 6e 31-00 73 74 61 heme.Sal oon1.sta
0000-6020: 72 64 72 6f-70 00 63 72-69 74 00 63-61 6e 63 65 rdrop.cr it.cance
0000-6030: 6c 00 77 69-6e 74 65 72-5f 64 61 79-5f 61 6d 62 l.winter _day_amb
0000-6040: 69 65 6e 74-00 53 74 61-64 69 75 6d-5f 63 68 65 ient.Sta dium_che
0000-6050: 65 72 00 53-74 61 64 69-75 6d 5f 61-6d 62 69 65 er.Stadi um_ambie
0000-6060: 6e 74 00 45-6d 69 6c 79-44 61 6e 63-65 00 45 6d nt.Emily Dance.Em
0000-6070: 69 6c 79 44-72 65 61 6d-00 45 6d 69-6c 79 54 68 ilyDream .EmilyTh
0000-6080: 65 6d 65 00-70 61 72 72-6f 74 00 73-68 61 6e 65 eme.parr ot.shane
0000-6090: 54 68 65 6d-65 00 62 69-67 44 65 53-65 6c 65 63 Theme.bi gDeSelec
0000-60a0: 74 00 62 69-67 53 65 6c-65 63 74 00-73 74 6f 6e t.bigSel ect.ston
0000-60b0: 65 43 72 61-63 6b 00 77-61 74 65 72-69 6e 67 43 eCrack.w ateringC
0000-60c0: 61 6e 00 63-6c 75 63 6b-00 73 77 6f-72 64 73 77 an.cluck .swordsw
0000-60d0: 69 70 65 00-73 6c 69 6d-65 64 65 61-64 00 45 61 ipe.slim edead.Ea
0000-60e0: 72 74 68 4d-69 6e 65 00-46 72 6f 73-74 4d 69 6e rthMine. FrostMin
0000-60f0: 65 00 4c 61-76 61 4d 69-6e 65 00 67-6f 61 74 00 e.LavaMi ne.goat.
0000-6100: 63 6f 77 00-53 70 72 69-6e 67 42 69-72 64 73 00 cow.Spri ngBirds.
0000-6110: 73 70 72 69-6e 67 73 6f-6e 67 73 00-53 68 69 70 springso ngs.Ship
0000-6120: 00 62 6f 6f-70 00 70 69-67 00 72 61-69 6e 73 6f .boop.pi g.rainso
0000-6130: 75 6e 64 00-70 75 72 63-68 61 73 65-00 64 61 67 und.purc hase.dag
0000-6140: 67 65 72 73-77 69 70 65-00 66 6c 79-62 75 7a 7a gerswipe .flybuzz
0000-6150: 69 6e 67 00-72 6f 63 6b-47 6f 6c 65-6d 48 69 74 ing.rock GolemHit
0000-6160: 00 70 61 72-72 79 00 73-65 61 67 75-6c 6c 73 00 .parry.s eagulls.
0000-6170: 63 6c 61 6e-6b 00 74 72-61 73 68 63-61 6e 6c 69 clank.tr ashcanli
0000-6180: 64 00 64 69-73 74 61 6e-74 54 72 61-69 6e 00 64 d.distan tTrain.d
0000-6190: 77 6f 70 00-62 75 62 62-6c 65 73 00-6d 6f 6e 65 wop.bubb les.mone
0000-61a0: 79 44 69 61-6c 00 67 75-6c 70 00 70-75 72 63 68 yDial.gu lp.purch
0000-61b0: 61 73 65 52-65 70 65 61-74 00 64 69-72 74 79 48 aseRepea t.dirtyH
0000-61c0: 69 74 00 66-69 73 68 69-6e 67 52 6f-64 42 65 6e it.fishi ngRodBen
0000-61d0: 64 00 77 61-74 65 72 53-6c 6f 73 68-00 43 6f 77 d.waterS losh.Cow
0000-61e0: 62 6f 79 5f-6d 6f 6e 73-74 65 72 44-69 65 00 50 boy_mons terDie.P
0000-61f0: 69 63 6b 75-70 5f 43 6f-69 6e 31 35-00 63 6f 77 ickup_Co in15.cow
0000-6200: 62 6f 79 5f-6d 6f 6e 73-74 65 72 68-69 74 00 73 boy_mons terhit.s
0000-6210: 6c 6f 73 68-00 77 6f 6f-64 57 68 61-63 6b 00 62 losh.woo dWhack.b
0000-6220: 61 72 72 65-6c 42 72 65-61 6b 00 64-6f 6f 72 43 arrelBre ak.doorC
0000-6230: 72 65 61 6b-00 64 6f 6f-72 4f 70 65-6e 00 63 72 reak.doo rOpen.cr
0000-6240: 6f 77 00 74-68 75 6e 64-65 72 5f 73-6d 61 6c 6c ow.thund er_small
0000-6245: 00 63 61 74-00 .cat.