1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-12-08 13:08:26 +00:00
This commit is contained in:
Kolo 2025-12-07 12:36:05 -05:00 committed by GitHub
commit ffd84abd66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 163 additions and 2 deletions

View file

@ -1117,6 +1117,9 @@ class Project extends HXProject
{
// This macro allows addition of new functionality to existing Flixel. -->
addHaxeMacro("addMetadata('@:build(funkin.util.macro.FlxMacro.buildFlxBasic())', 'flixel.FlxBasic')");
// This macro will go over every song in the assets folder and store them in an array to check for cheated scores.
addHaxeMacro("funkin.util.macro.SongDataValidator.loadSongData()");
}
function configureOutputDir()

View file

@ -10,6 +10,7 @@ import funkin.play.song.ScriptedSong;
import funkin.play.song.Song;
import funkin.util.assets.DataAssets;
import funkin.util.VersionUtil;
import funkin.util.macro.SongDataValidator;
import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
@ -51,6 +52,7 @@ using funkin.data.song.migrator.SongDataMigrator;
public override function loadEntries():Void
{
clearEntries();
SongDataValidator.clearLists();
//
// SCRIPTED ENTRIES
@ -499,11 +501,16 @@ using funkin.data.song.migrator.SongDataMigrator;
function loadEntryChartFile(id:String, ?variation:String):Null<JsonFile>
{
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-chart${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
if (!openfl.Assets.exists(entryFilePath)) return null;
var rawJson:String = openfl.Assets.getText(entryFilePath);
if (rawJson == null) return null;
rawJson = rawJson.trim();
SongDataValidator.checkChartValidity(rawJson, id, variation);
return {fileName: entryFilePath, contents: rawJson};
}

View file

@ -3380,8 +3380,9 @@ class PlayState extends MusicBeatSubState
var isNewHighscore = false;
var prevScoreData:Null<SaveScoreData> = Save.instance.getSongScore(currentSong.id, suffixedDifficulty);
var isChartValid:Bool = funkin.util.macro.SongDataValidator.isChartValid(currentSong.id, currentVariation);
if (currentSong != null && currentSong.validScore)
if (currentSong != null && currentSong.validScore && isChartValid)
{
// crackhead double thingie, sets whether was new highscore, AND saves the song!
var data =

View file

@ -569,7 +569,8 @@ class ResultState extends MusicBeatSubState
clearPercentCounter.curNumber = clearPercentTarget;
#if FEATURE_NEWGROUNDS
var isScoreValid = !(params?.isPracticeMode ?? false) && !(params?.isBotPlayMode ?? false);
var isChartValid:Bool = funkin.util.macro.SongDataValidator.isChartValid(params?.songId ?? "", params?.variationId ?? Constants.DEFAULT_VARIATION);
var isScoreValid = !(params?.isPracticeMode ?? false) && !(params?.isBotPlayMode ?? false) && isChartValid;
// This is the easiest spot to do the medal calculation lol.
if (isScoreValid && clearPercentTarget == 69) Medals.award(Nice);
#end

View file

@ -0,0 +1,149 @@
package funkin.util.macro;
import haxe.crypto.Sha1;
import haxe.rtti.Meta;
#if macro
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.io.Path;
import sys.FileSystem;
#end
class SongDataValidator
{
static var _allCharts:Map<String, String> = null;
static var _checkedCharts:Array<String> = [];
static var _invalidCharts:Array<String> = [];
/**
* See if the chart for the variation is valid, i.e. if the chart content differs from the compilation-time one.
* If it isn't, add it to an array of invalid charts.
* @param chartContent The content of the chart file.
*/
public static function checkChartValidity(chartContent:String, songId:String, variation:String = "default"):Void
{
var songFormat:String = '${songId}::${variation}';
// If the chart is already checked, do nothing.
if (_checkedCharts.contains(songFormat)) return;
// If the all charts list is null, fetch it from the class' type.
if (_allCharts == null)
{
var metaData:Dynamic = Meta.getType(SongDataValidator);
if (metaData.charts != null)
{
_allCharts = [];
for (element in (metaData.charts ?? []))
{
if (element.length != 2) throw 'Malformed element in chart datas: ' + element;
var song:String = element[0];
var data:String = element[1];
_allCharts.set(song, data);
}
}
else
{
throw 'No chart datas found in SongDataValidator';
}
}
var isValid:Bool = false;
// If there is no chart found for the song and variation, it's a custom song and it should always be valid.
if (!_allCharts.exists(songFormat))
{
isValid = true;
}
else
{
// Check if the content matches.
var chartClean:String = Sha1.encode(chartContent);
if (chartClean == _allCharts.get(songFormat)) isValid = true;
}
// Add to an array if the chart is invalid.
if (!isValid)
{
trace(' [WARN] The chart file for the song $songId and variation $variation has been tampered with.');
_invalidCharts.push(songFormat);
}
// Add the song to the checked charts so that we don't have to run checks again.
_checkedCharts.push(songFormat);
}
/**
* Returns true if the chart isn't in the invalid charts list.
*/
public static function isChartValid(songId:String, variation:String = "default"):Bool
{
return !_invalidCharts.contains('${songId}::${variation}');
}
/**
* Clear the lists so we can check for songs again.
*/
public static function clearLists():Void
{
_checkedCharts = [];
_invalidCharts = [];
}
#if macro
public static inline final BASE_PATH:String = "assets/preload/data/songs";
static var calledBefore:Bool = false;
#end
public static macro function loadSongData():Void
{
Context.onAfterTyping(function(_) {
if (calledBefore) return;
calledBefore = true;
var allCharts:Array<Expr> = [];
// Load songs from the assets folder.
var songs:Array<String> = FileSystem.readDirectory(BASE_PATH);
for (song in songs)
{
var songFiles:Array<String> = FileSystem.readDirectory(Path.join([BASE_PATH, song]));
for (file in songFiles)
{
if (!StringTools.endsWith(file, ".json")) continue; // Exclude non-json files.
var splitter:Array<String> = StringTools.replace(file, ".json", "").split("-");
if (splitter[1] != "chart") continue; // Exclude non-chart files.
var variation:String = splitter[2] ?? "default";
var chart:String = sys.io.File.getContent(Path.join([BASE_PATH, song, file]));
chart = Sha1.encode(StringTools.trim(chart));
var entry = [macro $v{'${song}::${variation}'}, macro $v{chart}];
allCharts.push(macro $a{entry});
}
}
// Add the chart data to the class.
var dataClass = Context.getType('funkin.util.macro.SongDataValidator');
switch (dataClass)
{
case TInst(t, _):
var dataClassType = t.get();
dataClassType.meta.remove('charts');
dataClassType.meta.add('charts', allCharts, Context.currentPos());
default:
throw 'Could not find SongDataValidator type';
}
});
}
}