Add proper HashLink support

Co-authored-by: Sinco <113909588+sphis-Sinco@users.noreply.github.com>
This commit is contained in:
Hyper_
2026-03-09 02:51:06 -06:00
committed by Hundrec
co-authored by Sinco
parent d569690a51
commit 340fd5dcf1
21 changed files with 112 additions and 59 deletions
+1 -2
View File
@@ -72,7 +72,6 @@
"haxecheckstyle.externalSourceRoots": [],
"haxecheckstyle.configurationFile": "checkstyle.json",
"haxecheckstyle.codeSimilarityBufferSize": 100,
"lime.projectFile": "project.hxp",
"lime.targets": [
{ "name": "windows", "enabled": true, "label": "Windows" },
{ "name": "mac", "enabled": true, "label": "macOS" },
@@ -80,8 +79,8 @@
{ "name": "html5", "enabled": true, "label": "HTML5" },
{ "name": "android", "enabled": true, "label": "Android" },
{ "name": "ios", "enabled": true, "label": "iOS" },
{ "name": "hl", "enabled": true, "label": "HashLink" },
// Disabled targets
{ "name": "hl", "enabled": false, "label": "HashLink" },
{ "name": "air", "enabled": false },
{ "name": "electron", "enabled": false },
{ "name": "flash", "enabled": false },
+1 -1
Submodule assets updated: bdea9cf4d9...4f8a4e1bd9
+4 -4
View File
@@ -4,7 +4,7 @@
"name": "FlxPartialSound",
"type": "git",
"dir": null,
"ref": "26ed20b9c181a576f4cbe18ccdea38912c01476e",
"ref": "2f984e244f1544ca98c0e03b9b21ae570f07ac55",
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
},
{
@@ -169,7 +169,7 @@
"name": "lime",
"type": "git",
"dir": null,
"ref": "cae0c0bc10915f6281625d07e5527f3cecb4347f",
"ref": "a3c915161ae66c24e76d968b1b6f4c08d61e314f",
"url": "https://github.com/FunkinCrew/lime"
},
{
@@ -183,14 +183,14 @@
"name": "openfl",
"type": "git",
"dir": null,
"ref": "5399946b68fd98f345d6b3e52b95ed6b78bf427c",
"ref": "92d7607f309c6c0705456a2e943f1aa0f052463d",
"url": "https://github.com/FunkinCrew/openfl"
},
{
"name": "polymod",
"type": "git",
"dir": null,
"ref": "6ed7a8940bacec2b474020fc0c155838ad280b8d",
"ref": "47f8f74a8498647cfbda2feb7fb8c943845b4c62",
"url": "https://github.com/FunkinCrew/polymod"
},
{
+9 -26
View File
@@ -114,11 +114,7 @@ class Project extends HXProject
*/
static final ANDROID_TARGET_SDK_VERSION:Int = 35;
static final ANDROID_EXTENSIONS:Array<String> = [
"funkin.extensions.CallbackUtil",
"funkin.extensions.FNFCExtension",
"funkin.extensions.AudioSession"
];
static final ANDROID_EXTENSIONS:Array<String> = ["funkin.extensions.CallbackUtil", "funkin.extensions.FNFCExtension", "funkin.extensions.AudioSession"];
/**
* The team ID to use for the iOS app. Configured in XCode.
@@ -171,11 +167,7 @@ class Project extends HXProject
/**
* Asset path globs to exclude on platforms with `CENSOR_EXPLETIVES` disabled.
*/
static final EXCLUDE_ASSETS_UNCENSORED:Array<String> = [
"stressCutscene-censored.mp4",
"stressPicoCutscene-censored.mp4",
"darnellCutscene-censored.mp4"
];
static final EXCLUDE_ASSETS_UNCENSORED:Array<String> = ["stressCutscene-censored.mp4", "stressPicoCutscene-censored.mp4", "darnellCutscene-censored.mp4"];
//
// ASSET EMBEDDING
@@ -894,7 +886,7 @@ class Project extends HXProject
// Should be true on desktop, non-tester builds.
// We don't want testers to accidentally leak songs to their Discord friends!
FEATURE_DISCORD_RPC.apply(this, isDesktop() && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
FEATURE_DISCORD_RPC.apply(this, isDesktop() && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this) && !isHashLink());
FEATURE_LOST_FOCUS_VOLUME.apply(this, isDesktop());
@@ -1228,21 +1220,12 @@ class Project extends HXProject
intentFilter.addChild(category);
}
final intentFilterDatas:Map<String, Array<String>> = [
"android:mimeType" => ["application/zip"],
// When this is added, it makes the game show up only when it's a FNFC file.
final intentFilterDatas:Map<String,
Array<String>> = ["android:mimeType" => ["application/zip"], // When this is added, it makes the game show up only when it's a FNFC file.
// But it also for some reason make it not show up at all in ZArchiver no matter what the file is...
// "android:host" => ["*"],
"android:scheme" => ["file", "content"],
// I think having all these makes it so the extension check would work even when the files has dots like "epic-song-v2.0.fnfc"
"android:pathPattern" => [
".*\\.fnfc",
".*\\..*\\.fnfc",
".*\\..*\\..*\\.fnfc",
".*\\..*\\..*\\..*\\.fnfc",
".*\\..*\\..*\\..*\\..*\\.fnfc"
]
];
"android:scheme" => ["file", "content"], // I think having all these makes it so the extension check would work even when the files has dots like "epic-song-v2.0.fnfc"
"android:pathPattern" => [".*\\.fnfc", ".*\\..*\\.fnfc", ".*\\..*\\..*\\.fnfc", ".*\\..*\\..*\\..*\\.fnfc", ".*\\..*\\..*\\..*\\..*\\.fnfc"]];
for (key => values in intentFilterDatas)
{
@@ -1351,12 +1334,12 @@ class Project extends HXProject
addHaxelib('thx.core'); // General utility library, "the lodash of Haxe"
addHaxelib('thx.semver'); // Version string handling
if (isDebug())
if (isDebug() && isCPP())
{
addHaxelib('hxcpp-debug-server'); // VSCode debug support
}
if (isDesktop() || isMobile() && !isHashLink() && FEATURE_VIDEO_PLAYBACK.isEnabled(this))
if ((isDesktop() || isMobile()) && FEATURE_VIDEO_PLAYBACK.isEnabled(this))
{
// hxvlc doesn't function on HashLink or non-desktop platforms
// It's also unnecessary if video playback is disabled
+11
View File
@@ -148,6 +148,17 @@ class Preferences
return DebugDisplayMode.Off;
#end
#if hl
// Account for when debugDisplay used to be a boolean
var options:Null<SaveDataOptions> = Save.instance?.options;
if (options != null && Std.isOfType(options.debugDisplay, Bool))
{
var convertedDebugDisplay = cast(options.debugDisplay, Bool) ? DebugDisplayMode.Simple : DebugDisplayMode.Off;
options.debugDisplay = convertedDebugDisplay;
Save.system.flush();
}
#end
return Save?.instance?.options?.debugDisplay ?? 'Off';
}
@@ -3,10 +3,14 @@ package funkin.audio.waveform;
@:nullSafety
class WaveformDataParser
{
@:native("int16_max")
static final INT16_MAX:Int = 32767;
@:native("int16_min")
static final INT16_MIN:Int = -32768;
@:native("int8_max")
static final INT8_MAX:Int = 127;
@:native("int8_min")
static final INT8_MIN:Int = -128;
public static function interpretFlxSound(sound:Null<flixel.sound.FlxSound>):Null<WaveformData>
+1 -1
View File
@@ -53,7 +53,7 @@ class DataError
case TEnum(c):
trace(' ERROR '.error() + '(${Type.getEnumName(c)}) ${e.toString()}');
default:
trace(' ERROR '.error() + '(${Type.typeof(e)}) ${e.toString()}');
trace(' ERROR '.error() + '(${Type.typeof(e)}) ${Std.string(e)}');
}
}
@@ -46,7 +46,7 @@ class OsuManiaImporter
{
var parts:Array<String> = line.split(":");
var key:String = StringTools.trim(parts.shift());
var value:String = StringTools.trim(parts.join(":"));
var value:Any = parseValue(StringTools.trim(parts.join(":")));
if (Reflect.field(result, currentSection) == null) Reflect.setField(result, currentSection, {
});
Reflect.setField(Reflect.field(result, currentSection), key, value);
@@ -200,4 +200,22 @@ class OsuManiaImporter
return new ManiaHitObject(time, noteD, holdDuration);
});
}
static function parseValue(v:String):Any
{
var result:Null<Any>;
if (v.contains('.'))
{
result = Std.parseFloat(v);
if (!Math.isNaN(result)) return result;
}
else
{
result = Std.parseInt(v);
if (result != null) return result;
}
return v;
}
}
+1 -1
View File
@@ -152,7 +152,7 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
// This is because of browser safeguards against timing attacks.
// See https://web.dev/coop-coep to enable headers which allow for more precise timestamps.
return haxe.Int64.fromFloat(js.Browser.window.performance.now()) * NS_PER_MS;
#elseif cpp
#elseif lime_cffi
// NOTE: If the game hard crashes on this line, rebuild Lime!
// `lime rebuild windows -clean`
return lime._internal.backend.native.NativeCFFI.lime_sdl_get_ticks() * NS_PER_MS;
+8 -3
View File
@@ -70,6 +70,7 @@ class VideoCutscene
{
if (PlayState.instance == null) return;
#if FEATURE_VIDEO_PLAYBACK
if (!openfl.Assets.exists(filePath))
{
// Display a popup.
@@ -78,8 +79,7 @@ class VideoCutscene
return;
}
var rawFilePath = Paths.stripLibrary(filePath);
#end
// Trigger the cutscene. Don't play the song in the background.
PlayState.instance.isInCutscene = true;
@@ -100,13 +100,18 @@ class VideoCutscene
}
#end
#if NO_FEATURE_VIDEO_PLAYBACK
trace(" WARNING ".bold().bg_yellow() + " Video playback is not enabled. Calling video end callback instead.");
finishVideo();
#else
#if html5
playVideoHTML5(rawFilePath);
playVideoHTML5(Paths.stripLibrary(filePath));
#elseif hxvlc
playVideoNative(filePath);
#else
throw "No video support for this platform!";
#end
#end
}
public static function isPlaying():Bool
@@ -48,6 +48,10 @@ class IntroSubState extends MusicBeatSubState
playVideoNative(LIGHTS_VIDEO_PATH);
#end
#if NO_FEATURE_VIDEO_PLAYBACK
onLightsEnd();
#end
// // Im TOO lazy to even care, so uh, yep
// FlxG.camera.zoom = 0.66666666666666666666666666666667;
// vid.x = -(FlxG.width - (FlxG.width * FlxG.camera.zoom));
+2 -2
View File
@@ -11,7 +11,7 @@ import openfl.text.TextFormat;
/**
* A debug overlay showing useful info.
*/
#if cpp
#if lime_cffi
@:access(lime._internal.backend.native.NativeCFFI)
#end
class FunkinDebugDisplay extends Sprite
@@ -149,7 +149,7 @@ class FunkinDebugDisplay extends Sprite
override function __enterFrame(deltaTime:Int):Void
{
#if cpp
#if lime_cffi
final currentTime:Float = lime._internal.backend.native.NativeCFFI.lime_sdl_get_ticks();
#elseif html5
final currentTime:Float = js.Browser.window.performance.now();
@@ -3455,7 +3455,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (audioInstTrack != null) audioInstTrack.volume = volume;
menubarLabelVolumeInstrumental.text = 'Instrumental - ${Std.int(event.value)}%';
};
previousAudioVolumes[3] = menubarLabelVolumeInstrumental.value;
previousAudioVolumes[3] = menubarItemVolumeInstrumental.value;
menubarItemVolumeVocalsPlayer.onChange = event ->
{
@@ -3463,7 +3463,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
audioVocalTrackGroup.playerVolume = volume;
menubarLabelVolumeVocalsPlayer.text = 'Player - ${Std.int(event.value)}%';
};
previousAudioVolumes[4] = menubarLabelVolumeVocalsPlayer.value;
previousAudioVolumes[4] = menubarItemVolumeVocalsPlayer.value;
menubarItemVolumeVocalsOpponent.onChange = event ->
{
@@ -3471,7 +3471,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
audioVocalTrackGroup.opponentVolume = volume;
menubarLabelVolumeVocalsOpponent.text = 'Enemy - ${Std.int(event.value)}%';
};
previousAudioVolumes[5] = menubarLabelVolumeVocalsOpponent.value;
previousAudioVolumes[5] = menubarItemVolumeVocalsOpponent.value;
menubarItemPlaybackSpeed.onChange = event ->
{
@@ -4071,7 +4071,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
eventSprite.parentState = this;
// trace('Creating new Event... (${renderedEvents.members.length})');
if (eventData?.value != null && (eventData?.value?.ease != null && eventData?.value?.easeDir == null))
if (eventData?.value != null && (eventData.getString("ease") != null && eventData.getInt("easeDir") == null))
{
eventData.value = migrateEventEaseDirectionFields(eventData.value);
}
@@ -88,8 +88,16 @@ class ChartEditorWelcomeDialog extends ChartEditorBaseDialog
linkRecentChart.tooltip = chartPath;
#if sys
var lastModified:String = "Last Modified: " + sys.FileSystem.stat(chartPath).mtime.toString();
linkRecentChart.tooltip += "\n" + lastModified;
if (!FileUtil.fileExists(chartPath))
{
trace('Previously loaded chart file (${chartPath}) does not exist, disabling link...');
linkRecentChart.disabled = true;
}
else
{
var lastModified:String = "Last Modified: " + sys.FileSystem.stat(chartPath).mtime.toString();
linkRecentChart.tooltip += "\n" + lastModified;
}
#end
linkRecentChart.onClick = function(_event)
@@ -111,12 +119,6 @@ class ChartEditorWelcomeDialog extends ChartEditorBaseDialog
}
}
if (!FileUtil.fileExists(chartPath))
{
trace('Previously loaded chart file (${chartPath}) does not exist, disabling link...');
linkRecentChart.disabled = true;
}
splashRecentContainer.addComponent(linkRecentChart);
}
@@ -374,7 +374,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
{
if (field.type == FRAME) return;
var value = event.target.value;
var value:Any = event.target.value;
if (field.type == ENUM)
{
var drp:DropDown = cast event.target;
@@ -57,7 +57,7 @@ class StageEditorCharacterToolbox extends StageEditorDefaultToolbox
charCamX.onChange = charCamY.onChange = function(_)
{
if (state.selectedChar == null) return;
state.charCamOffsets[state.selectedChar.characterType] = [charCamX.pos, charCamY.pos];
state.charCamOffsets[state.selectedChar.characterType] = [charCamX.pos ?? 0, charCamY.pos ?? 0];
state.updateMarkerPos();
}
+6 -1
View File
@@ -14,9 +14,14 @@ class SongItemGroup extends FlxTypedGroup<SongMenuItem>
var rankBlurredShader:GaussianBlurShader = new GaussianBlurShader(1);
var favIconBlurredShader:GaussianBlurShader = new GaussianBlurShader(1.2);
#if hl
// What the hell is the compiler about on here?
override function recycle(?cls:Class<Dynamic>, ?factory:Void->Dynamic, force:Bool = false, revive:Bool = true):SongMenuItem
#else
override function recycle(?cls:Class<SongMenuItem>, ?factory:Void->SongMenuItem, force:Bool = false, revive:Bool = true):SongMenuItem
#end
{
var capsule:SongMenuItem = super.recycle(cls, factory, force, revive);
var capsule:SongMenuItem = super.recycle(#if hl cast #end cls, factory, force, revive);
// Apply the same shader instance to some elements so that we can use one draw call to render multiple of them.
capsule.fakeBlurredRanking.shader = rankBlurredShader;
+8
View File
@@ -65,7 +65,9 @@ class TitleState extends MusicBeatState
var titleText:FunkinSprite;
var maskShader = new LeftMaskShader();
#if FEATURE_VIDEO_PLAYBACK
var attractTimer:FlxTimer;
#end
function startIntro():Void
{
@@ -155,8 +157,10 @@ class TitleState extends MusicBeatState
else
initialized = true;
#if FEATURE_VIDEO_PLAYBACK
trace('Opening Attract state in ${Constants.TITLE_ATTRACT_DELAY} seconds...');
attractTimer = new FlxTimer().start(Constants.TITLE_ATTRACT_DELAY, (_:FlxTimer) -> moveToAttract());
#end
}
/**
@@ -286,11 +290,13 @@ class TitleState extends MusicBeatState
function moveToMainMenu():Void
{
#if FEATURE_VIDEO_PLAYBACK
if (attractTimer != null)
{
attractTimer.cancel();
attractTimer = null;
}
#end
funkin.FunkinMemory.purgeCache();
FlxG.switchState(() -> new MainMenuState());
@@ -339,8 +345,10 @@ class TitleState extends MusicBeatState
FlxG.camera.flash(FlxColor.WHITE, 1);
FunkinSound.playOnce(Paths.sound('confirmMenu'), 0.7);
#if FEATURE_VIDEO_PLAYBACK
// Stop the attract timer so you can listen to the whole song!
attractTimer.cancel();
#end
}
function createCoolText(textArray:Array<String>):Void
+12
View File
@@ -38,6 +38,11 @@ class MemoryUtil
#elseif js
var result:String = 'JS-MNS:';
result += '\n- Memory Used: ${getGCMemory()} bytes';
#elseif hl
var result:String = 'HL-MNNS:';
result += '\n- Memory Used: ${hl.Gc.stats().currentMemory} bytes';
result += '\n- Memory Allocated: ${hl.Gc.stats().totalAllocated} bytes';
result += '\n- Memory Allocation Count: ${hl.Gc.stats().allocationCount}';
#else
var result:String = 'Unknown GC';
#end
@@ -110,6 +115,8 @@ class MemoryUtil
{
#if cpp
cpp.vm.Gc.enable(true);
#elseif hl
hl.Gc.enable(true);
#else
throw 'Not implemented!';
#end
@@ -122,6 +129,8 @@ class MemoryUtil
{
#if cpp
cpp.vm.Gc.enable(false);
#elseif hl
hl.Gc.enable(false);
#else
throw 'Not implemented!';
#end
@@ -136,6 +145,9 @@ class MemoryUtil
{
#if cpp
cpp.vm.Gc.run(major);
#elseif hl
// Doesn't seem to have just a collect function?
hl.Gc.major();
#else
throw 'Not implemented!';
#end
+1 -1
View File
@@ -68,7 +68,7 @@ class SerializerUtil
/**
* Customize how certain types are serialized when converting to JSON.
*/
static function replacer(key:String, value:Dynamic):Dynamic
static function replacer(key:Dynamic, value:Dynamic):Dynamic
{
// Hacky because you can't use `isOfType` on a struct.
if (key == "version")
+4 -2
View File
@@ -7,7 +7,9 @@ import haxe.macro.Expr;
#end
#if (linux && !macro)
#if cpp
import hxgamemode.GamemodeClient;
#end
@:image('art/icons/iconOG.png')
class ApplicationIcon extends lime.graphics.Image {}
@@ -56,7 +58,7 @@ class ApplicationMain
public static function create(config):Void
{
#if linux
#if (linux && cpp)
GamemodeClient.request_start();
#end
@@ -192,7 +194,7 @@ class ApplicationMain
lime.system.System.exit(result);
#end
#if linux
#if (linux && cpp)
GamemodeClient.request_end();
#end
}