Backport custom song support. Prefs are CustomSongsEnable, CustomSongsMaxCount, CustomSongsLoadTimeout, CustomSongsMaxSeconds, CustomSongsMaxMegabytes. Metrics are NumProfileSongGroupColors and the colors with it. Profile custom load function is passed PlayerNumber now. Stepmania must run as root to save USB profile scores. (#1470)

This commit is contained in:
Kyzentun
2017-06-06 05:30:06 -07:00
committed by Colby Klein
parent 1c920a4197
commit 2aee27a276
22 changed files with 627 additions and 240 deletions
+295 -229
View File
@@ -14,7 +14,9 @@
#include "TitleSubstitution.h"
#include "BannerCache.h"
//#include "BackgroundCache.h"
#include "ProfileManager.h"
#include "Sprite.h"
#include "RageFile.h"
#include "RageFileManager.h"
#include "RageSurface.h"
#include "RageTextureManager.h"
@@ -271,7 +273,7 @@ static set<RString> BlacklistedImages;
* HasMusic(), HasBanner() or GetHashForDirectory().
* If true, check the directory hash and reload the song from scratch if it's changed.
*/
bool Song::LoadFromSongDir( RString sDir, bool load_autosave )
bool Song::LoadFromSongDir(RString sDir, bool load_autosave, ProfileSlot from_profile)
{
// LOG->Trace( "Song::LoadFromSongDir(%s)", sDir.c_str() );
ASSERT_M( sDir != "", "Songs can't be loaded from an empty directory!" );
@@ -283,26 +285,40 @@ bool Song::LoadFromSongDir( RString sDir, bool load_autosave )
// save song dir
m_sSongDir = sDir;
bool use_cache = true;
// save group name
vector<RString> sDirectoryParts;
split( m_sSongDir, "/", sDirectoryParts, false );
ASSERT( sDirectoryParts.size() >= 4 ); /* e.g. "/Songs/Slow/Taps/" */
m_sGroupName = sDirectoryParts[sDirectoryParts.size()-3]; // second from last item
ASSERT( m_sGroupName != "" );
if(from_profile == ProfileSlot_Invalid)
{
vector<RString> sDirectoryParts;
split( m_sSongDir, "/", sDirectoryParts, false );
ASSERT( sDirectoryParts.size() >= 4 ); /* e.g. "/Songs/Slow/Taps/" */
m_sGroupName = sDirectoryParts[sDirectoryParts.size()-3]; // second from last item
ASSERT( m_sGroupName != "" );
}
else
{
m_LoadedFromProfile= from_profile;
m_sGroupName= PROFILEMAN->GetProfile(from_profile)->m_sDisplayName;
use_cache= false;
}
// First, look in the cache for this song (without loading NoteData)
unsigned uCacheHash = SONGINDEX->GetCacheHash(m_sSongDir);
bool bUseCache = true;
RString sCacheFilePath = GetCacheFilePath();
RString cache_file_path;
if(m_LoadedFromProfile == ProfileSlot_Invalid)
{
// First, look in the cache for this song (without loading NoteData)
unsigned uCacheHash = SONGINDEX->GetCacheHash(m_sSongDir);
cache_file_path = GetCacheFilePath();
if( !DoesFileExist(sCacheFilePath) )
{ bUseCache = false; }
else if(!PREFSMAN->m_bFastLoad && GetHashForDirectory(m_sSongDir) != uCacheHash)
{ bUseCache = false; } // this cache is out of date
else if(load_autosave)
{ bUseCache= false; }
if( !DoesFileExist(cache_file_path) )
{ use_cache = false; }
else if(!PREFSMAN->m_bFastLoad && GetHashForDirectory(m_sSongDir) != uCacheHash)
{ use_cache = false; } // this cache is out of date
else if(load_autosave)
{ use_cache= false; }
}
if( bUseCache )
if(use_cache)
{
/*
LOG->Trace("Loading '%s' from cache file '%s'.",
@@ -310,12 +326,12 @@ bool Song::LoadFromSongDir( RString sDir, bool load_autosave )
GetCacheFilePath().c_str());
*/
SSCLoader loaderSSC;
bool bLoadedFromSSC = loaderSSC.LoadFromSimfile( sCacheFilePath, *this, true );
bool bLoadedFromSSC = loaderSSC.LoadFromSimfile( cache_file_path, *this, true );
if( !bLoadedFromSSC )
{
// load from .sm
SMLoader loaderSM;
loaderSM.LoadFromSimfile( sCacheFilePath, *this, true );
loaderSM.LoadFromSimfile( cache_file_path, *this, true );
loaderSM.TidyUpData( *this, true );
}
if(m_sMainTitle == "" || (m_sMusicFile == "" && m_vsKeysoundFile.empty()))
@@ -335,13 +351,12 @@ bool Song::LoadFromSongDir( RString sDir, bool load_autosave )
{
LOG->UserLog( "Song", sDir, "has no SSC, SM, SMA, DWI, BMS, or KSF files." );
vector<RString> vs;
GetDirListing( sDir + "*.mp3", vs, false, false );
GetDirListing( sDir + "*.oga", vs, false, false );
GetDirListing( sDir + "*.ogg", vs, false, false );
bool bHasMusic = !vs.empty();
vector<RString> audios;
FILEMAN->GetDirListingWithMultipleExtensions(sDir,
ActorUtil::GetTypeExtensionList(FT_Sound), audios);
bool has_music = !audios.empty();
if( !bHasMusic )
if(!has_music)
{
LOG->UserLog( "Song", sDir, "has no music file either. Ignoring this song directory." );
return false;
@@ -361,16 +376,52 @@ bool Song::LoadFromSongDir( RString sDir, bool load_autosave )
// Don't save a cache file if the autosave is being loaded, because the
// cache file would contain the autosave filename. -Kyz
if(!load_autosave)
// Songs loaded from removable profile are never cached, on the
// assumption that they'll change frequently and have invalid cache
// entries. -Kyz
if(!load_autosave && m_LoadedFromProfile == ProfileSlot_Invalid)
{
// save a cache file so we don't have to parse it all over again next time
if(!SaveToCacheFile())
{ sCacheFilePath = RString(); }
{ cache_file_path = RString(); }
}
}
if(m_LoadedFromProfile != ProfileSlot_Invalid)
{
if(m_fMusicLengthSeconds > PREFSMAN->m_custom_songs_max_seconds)
{
LOG->Trace("Custom song %s is too long.", m_sSongDir.c_str());
return false;
}
RageFile music;
if(!music.Open(GetMusicPath(), RageFile::READ))
{
LOG->Trace("Custom song %s could not open music.", m_sSongDir.c_str());
return false;
}
if(music.GetFileSize() > PREFSMAN->m_custom_songs_max_megabytes * 1000000)
{
LOG->Trace("Custom song %s music file is too big.", m_sSongDir.c_str());
return false;
}
m_pre_customify_song_dir= m_sSongDir;
m_sSongDir= custom_songify_path(m_sSongDir);
m_sBannerFile.clear();
m_sJacketFile.clear();
m_sCDFile.clear();
m_sDiscFile.clear();
m_sLyricsFile.clear();
m_sBackgroundFile.clear();
m_sCDTitleFile.clear();
}
FOREACH( Steps*, m_vpSteps, s )
{
if(m_LoadedFromProfile != ProfileSlot_Invalid)
{
(*s)->ChangeFilenamesForCustomSong();
}
/* Compress all Steps. During initial caching, this will remove cached
* NoteData; during cached loads, this will just remove cached SMData. */
(*s)->Compress();
@@ -653,10 +704,15 @@ void Song::TidyUpData( bool from_cache, bool /* duringCache */ )
fill_exts.reserve(4);
lists_to_fill.push_back(&music_list);
fill_exts.push_back(&ActorUtil::GetTypeExtensionList(FT_Sound));
lists_to_fill.push_back(&image_list);
fill_exts.push_back(&ActorUtil::GetTypeExtensionList(FT_Bitmap));
lists_to_fill.push_back(&movie_list);
fill_exts.push_back(&ActorUtil::GetTypeExtensionList(FT_Movie));
// Disable bg and banner images and movies for custom songs. Loading
// them takes too long. -Kyz
if(m_LoadedFromProfile == ProfileSlot_Invalid)
{
lists_to_fill.push_back(&image_list);
fill_exts.push_back(&ActorUtil::GetTypeExtensionList(FT_Bitmap));
lists_to_fill.push_back(&movie_list);
fill_exts.push_back(&ActorUtil::GetTypeExtensionList(FT_Movie));
}
lists_to_fill.push_back(&lyric_list);
fill_exts.push_back(&lyric_extensions);
for(vector<RString>::iterator filename= song_dir_listing.begin();
@@ -801,85 +857,6 @@ void Song::TidyUpData( bool from_cache, bool /* duringCache */ )
}
// Here's the problem: We have a directory full of images. We want to
// determine which image is the banner, which is the background, and
// which is the CDTitle.
// For blank args to FindFirstFilenameContaining. -Kyz
vector<RString> empty_list;
bool has_jacket= HasJacket();
bool has_cdimage= HasCDImage();
bool has_disc= HasDisc();
bool has_cdtitle= HasCDTitle();
// First, check the file name for hints.
if(!m_bHasBanner)
{
/* If a nonexistant banner file is specified, and we can't find a
* replacement, don't wipe out the old value. */
//m_sBannerFile = "";
// find an image with "banner" in the file name
vector<RString> contains(1, "banner");
/* Some people do things differently for the sake of being different.
* Don't match eg. abnormal, numbness. */
vector<RString> ends_with(1, " bn");
m_bHasBanner= FindFirstFilenameContaining(image_list,
m_sBannerFile, empty_list, contains, ends_with);
}
if(!m_bHasBackground)
{
//m_sBackgroundFile = "";
// find an image with "bg" or "background" in the file name
vector<RString> contains(1, "background");
vector<RString> ends_with(1, "bg");
m_bHasBackground= FindFirstFilenameContaining(image_list,
m_sBackgroundFile, empty_list, contains, ends_with);
}
if(!has_jacket)
{
// find an image with "jacket" or "albumart" in the filename.
vector<RString> starts_with(1, "jk_");
vector<RString> contains;
contains.reserve(2);
contains.push_back("jacket");
contains.push_back("albumart");
has_jacket= FindFirstFilenameContaining(image_list,
m_sJacketFile, starts_with, contains, empty_list);
}
if(!has_cdimage)
{
// CD image, a la ddr 1st-3rd (not to be confused with CDTitles)
// find an image with "-cd" at the end of the filename.
vector<RString> ends_with(1, "-cd");
has_cdimage= FindFirstFilenameContaining(image_list,
m_sCDFile, empty_list, empty_list, ends_with);
}
if(!has_disc)
{
// a rectangular graphic, not to be confused with CDImage above.
vector<RString> ends_with;
ends_with.reserve(2);
ends_with.push_back(" disc");
ends_with.push_back(" title");
has_disc= FindFirstFilenameContaining(image_list,
m_sDiscFile, empty_list, empty_list, ends_with);
}
if(!has_cdtitle)
{
// find an image with "cdtitle" in the file name
vector<RString> contains(1, "cdtitle");
has_cdtitle= FindFirstFilenameContaining(image_list,
m_sCDTitleFile, empty_list, contains, empty_list);
}
if(!HasLyrics())
{
// Check if there is a lyric file in here
@@ -889,155 +866,238 @@ void Song::TidyUpData( bool from_cache, bool /* duringCache */ )
}
}
/* Now, For the images we still haven't found,
* look at the image dimensions of the remaining unclassified images. */
for(unsigned int i= 0; i < image_list.size(); ++i) // foreach image
// Since banner, bg, and cdtitle are disabled for custom songs, don't try
// to find them. -Kyz
if(m_LoadedFromProfile == ProfileSlot_Invalid)
{
if(m_bHasBanner && m_bHasBackground && has_cdtitle)
// Here's the problem: We have a directory full of images. We want to
// determine which image is the banner, which is the background, and
// which is the CDTitle.
// For blank args to FindFirstFilenameContaining. -Kyz
vector<RString> empty_list;
bool has_jacket= HasJacket();
bool has_cdimage= HasCDImage();
bool has_disc= HasDisc();
bool has_cdtitle= HasCDTitle();
// First, check the file name for hints.
if(!m_bHasBanner)
{
/* If a nonexistant banner file is specified, and we can't find a
* replacement, don't wipe out the old value. */
//m_sBannerFile = "";
// find an image with "banner" in the file name
vector<RString> contains(1, "banner");
/* Some people do things differently for the sake of being different.
* Don't match eg. abnormal, numbness. */
vector<RString> ends_with(1, " bn");
m_bHasBanner= FindFirstFilenameContaining(image_list,
m_sBannerFile, empty_list, contains, ends_with);
}
if(!m_bHasBackground)
{
//m_sBackgroundFile = "";
// find an image with "bg" or "background" in the file name
vector<RString> contains(1, "background");
vector<RString> ends_with(1, "bg");
m_bHasBackground= FindFirstFilenameContaining(image_list,
m_sBackgroundFile, empty_list, contains, ends_with);
}
if(!has_jacket)
{
// find an image with "jacket" or "albumart" in the filename.
vector<RString> starts_with(1, "jk_");
vector<RString> contains;
contains.reserve(2);
contains.push_back("jacket");
contains.push_back("albumart");
has_jacket= FindFirstFilenameContaining(image_list,
m_sJacketFile, starts_with, contains, empty_list);
}
if(!has_cdimage)
{
// CD image, a la ddr 1st-3rd (not to be confused with CDTitles)
// find an image with "-cd" at the end of the filename.
vector<RString> ends_with(1, "-cd");
has_cdimage= FindFirstFilenameContaining(image_list,
m_sCDFile, empty_list, empty_list, ends_with);
}
if(!has_disc)
{
// a rectangular graphic, not to be confused with CDImage above.
vector<RString> ends_with;
ends_with.reserve(2);
ends_with.push_back(" disc");
ends_with.push_back(" title");
has_disc= FindFirstFilenameContaining(image_list,
m_sDiscFile, empty_list, empty_list, ends_with);
}
if(!has_cdtitle)
{
// find an image with "cdtitle" in the file name
vector<RString> contains(1, "cdtitle");
has_cdtitle= FindFirstFilenameContaining(image_list,
m_sCDTitleFile, empty_list, contains, empty_list);
}
/* Now, For the images we still haven't found,
* look at the image dimensions of the remaining unclassified images. */
for(unsigned int i= 0; i < image_list.size(); ++i) // foreach image
{
if(m_bHasBanner && m_bHasBackground && has_cdtitle)
break; // done
// ignore DWI "-char" graphics
RString lower = image_list[i];
lower.MakeLower();
if(BlacklistedImages.find(lower) != BlacklistedImages.end())
// ignore DWI "-char" graphics
RString lower = image_list[i];
lower.MakeLower();
if(BlacklistedImages.find(lower) != BlacklistedImages.end())
continue; // skip
// Skip any image that we've already classified
// Skip any image that we've already classified
if(m_bHasBanner && m_sBannerFile.EqualsNoCase(image_list[i]))
if(m_bHasBanner && m_sBannerFile.EqualsNoCase(image_list[i]))
continue; // skip
if(m_bHasBackground && m_sBackgroundFile.EqualsNoCase(image_list[i]))
if(m_bHasBackground && m_sBackgroundFile.EqualsNoCase(image_list[i]))
continue; // skip
if(has_cdtitle && m_sCDTitleFile.EqualsNoCase(image_list[i]))
if(has_cdtitle && m_sCDTitleFile.EqualsNoCase(image_list[i]))
continue; // skip
if(has_jacket && m_sJacketFile.EqualsNoCase(image_list[i]))
if(has_jacket && m_sJacketFile.EqualsNoCase(image_list[i]))
continue; // skip
if(has_disc && m_sDiscFile.EqualsNoCase(image_list[i]))
if(has_disc && m_sDiscFile.EqualsNoCase(image_list[i]))
continue; // skip
if(has_cdimage && m_sCDFile.EqualsNoCase(image_list[i]))
if(has_cdimage && m_sCDFile.EqualsNoCase(image_list[i]))
continue; // skip
RString sPath = m_sSongDir + image_list[i];
RString sPath = m_sSongDir + image_list[i];
// We only care about the dimensions.
RString error;
RageSurface *img = RageSurfaceUtils::LoadFile(sPath, error, true);
if(!img)
{
LOG->UserLog("Graphic file", sPath, "couldn't be loaded: %s", error.c_str());
continue;
}
const int width = img->w;
const int height = img->h;
delete img;
if(!m_bHasBackground && width >= 320 && height >= 240)
{
m_sBackgroundFile = image_list[i];
m_bHasBackground= true;
continue;
}
if(!m_bHasBanner && 100 <= width && width <= 320 &&
50 <= height && height <= 240)
{
m_sBannerFile = image_list[i];
m_bHasBanner= true;
continue;
}
/* Some songs have overlarge banners. Check if the ratio is reasonable
* (over 2:1; usually over 3:1), and large (not a cdtitle). */
if(!m_bHasBanner && width > 200 && float(width) / height > 2.0f)
{
m_sBannerFile = image_list[i];
m_bHasBanner= true;
continue;
}
/* Agh. DWI's inline title images are triggering this, resulting in
* kanji, etc., being used as a CDTitle for songs with none. Some
* sample data from random incarnations:
* 42x50 35x50 50x50 144x49
* It looks like ~50 height is what people use to align to DWI's font.
*
* My tallest CDTitle is 44. Let's cut off in the middle and hope for
* the best. -(who? -aj) */
/* The proper size of a CDTitle is 64x48 or sometihng. Simfile artists
* typically don't give a shit about this (see Cetaka's fucking banner
* -sized CDTitle). This is also subverted in certain designs (beta
* Mungyodance 3 simfiles, for instance, used the CDTitle to hold
* various information about the song in question). As it stands,
* I'm keeping this code until I figure out wtf to do -aj
*/
if(!has_cdtitle && width <= 100 && height <= 48)
{
m_sCDTitleFile = image_list[i];
has_cdtitle= true;
continue;
}
// Jacket files typically have the same width and height.
if(!has_jacket && width == height)
{
m_sJacketFile = image_list[i];
has_jacket= true;
continue;
}
// Disc images are typically rectangular; make sure we have a banner already.
if(!has_disc && (width > height) && m_bHasBanner)
{
if(image_list[i] != m_sBannerFile)
// We only care about the dimensions.
RString error;
RageSurface *img = RageSurfaceUtils::LoadFile(sPath, error, true);
if(!img)
{
m_sDiscFile = image_list[i];
has_disc= true;
LOG->UserLog("Graphic file", sPath, "couldn't be loaded: %s", error.c_str());
continue;
}
continue;
}
// CD images are the same as Jackets, typically the same width and height
if(!has_cdimage && width == height)
{
m_sCDFile = image_list[i];
has_cdimage= true;
continue;
const int width = img->w;
const int height = img->h;
delete img;
if(!m_bHasBackground && width >= 320 && height >= 240)
{
m_sBackgroundFile = image_list[i];
m_bHasBackground= true;
continue;
}
if(!m_bHasBanner && 100 <= width && width <= 320 &&
50 <= height && height <= 240)
{
m_sBannerFile = image_list[i];
m_bHasBanner= true;
continue;
}
/* Some songs have overlarge banners. Check if the ratio is reasonable
* (over 2:1; usually over 3:1), and large (not a cdtitle). */
if(!m_bHasBanner && width > 200 && float(width) / height > 2.0f)
{
m_sBannerFile = image_list[i];
m_bHasBanner= true;
continue;
}
/* Agh. DWI's inline title images are triggering this, resulting in
* kanji, etc., being used as a CDTitle for songs with none. Some
* sample data from random incarnations:
* 42x50 35x50 50x50 144x49
* It looks like ~50 height is what people use to align to DWI's font.
*
* My tallest CDTitle is 44. Let's cut off in the middle and hope for
* the best. -(who? -aj) */
/* The proper size of a CDTitle is 64x48 or sometihng. Simfile artists
* typically don't give a shit about this (see Cetaka's fucking banner
* -sized CDTitle). This is also subverted in certain designs (beta
* Mungyodance 3 simfiles, for instance, used the CDTitle to hold
* various information about the song in question). As it stands,
* I'm keeping this code until I figure out wtf to do -aj
*/
if(!has_cdtitle && width <= 100 && height <= 48)
{
m_sCDTitleFile = image_list[i];
has_cdtitle= true;
continue;
}
// Jacket files typically have the same width and height.
if(!has_jacket && width == height)
{
m_sJacketFile = image_list[i];
has_jacket= true;
continue;
}
// Disc images are typically rectangular; make sure we have a banner already.
if(!has_disc && (width > height) && m_bHasBanner)
{
if(image_list[i] != m_sBannerFile)
{
m_sDiscFile = image_list[i];
has_disc= true;
}
continue;
}
// CD images are the same as Jackets, typically the same width and height
if(!has_cdimage && width == height)
{
m_sCDFile = image_list[i];
has_cdimage= true;
continue;
}
}
}
// If no BGChanges are specified and there are movies in the song
// directory, then assume they are DWI style where the movie begins at
// beat 0.
if(!HasBGChanges())
{
/* Use this->GetBeatFromElapsedTime(0) instead of 0 to start when the
* music starts. */
if(movie_list.size() == 1)
// If no BGChanges are specified and there are movies in the song
// directory, then assume they are DWI style where the movie begins at
// beat 0.
if(!HasBGChanges())
{
this->AddBackgroundChange(BACKGROUND_LAYER_1,
BackgroundChange(0, movie_list[0], "", 1.f,
SBE_StretchNoLoop));
/* Use this->GetBeatFromElapsedTime(0) instead of 0 to start when the
* music starts. */
if(movie_list.size() == 1)
{
this->AddBackgroundChange(BACKGROUND_LAYER_1,
BackgroundChange(0, movie_list[0], "", 1.f,
SBE_StretchNoLoop));
}
}
// Clear fields for files that turned out to not exist.
#define CLEAR_NOT_HAS(has_name, field_name) if(!has_name) { field_name= ""; }
CLEAR_NOT_HAS(m_bHasBanner, m_sBannerFile);
CLEAR_NOT_HAS(m_bHasBackground, m_sBackgroundFile);
CLEAR_NOT_HAS(has_jacket, m_sJacketFile);
CLEAR_NOT_HAS(has_cdimage, m_sCDFile);
CLEAR_NOT_HAS(has_disc, m_sDiscFile);
CLEAR_NOT_HAS(has_cdtitle, m_sCDTitleFile);
#undef CLEAR_NOT_HAS
}
// Don't allow multiple Steps of the same StepsType and Difficulty
// (except for edits). We should be able to use difficulty names as
// unique identifiers for steps. */
SongUtil::AdjustDuplicateSteps(this);
// Clear fields for files that turned out to not exist.
#define CLEAR_NOT_HAS(has_name, field_name) if(!has_name) { field_name= ""; }
CLEAR_NOT_HAS(m_bHasBanner, m_sBannerFile);
CLEAR_NOT_HAS(m_bHasBackground, m_sBackgroundFile);
CLEAR_NOT_HAS(has_jacket, m_sJacketFile);
CLEAR_NOT_HAS(has_cdimage, m_sCDFile);
CLEAR_NOT_HAS(has_disc, m_sDiscFile);
CLEAR_NOT_HAS(has_cdtitle, m_sCDTitleFile);
#undef CLEAR_NOT_HAS
}
/* Generate these before we autogen notes, so the new notes can inherit
@@ -2140,6 +2200,11 @@ public:
lua_pushboolean(L, p->GetEnabled());
return 1;
}
static int IsCustomSong(T* p, lua_State* L)
{
lua_pushboolean(L, p->m_LoadedFromProfile != ProfileSlot_Invalid);
return 1;
}
static int GetGroupName( T* p, lua_State *L )
{
lua_pushstring(L, p->m_sGroupName);
@@ -2362,6 +2427,7 @@ public:
ADD_METHOD( GetSongFilePath );
ADD_METHOD( IsTutorial );
ADD_METHOD( IsEnabled );
ADD_METHOD(IsCustomSong);
ADD_METHOD( GetGroupName );
ADD_METHOD( MusicLengthSeconds );
ADD_METHOD( GetSampleStart );