Merge remote-tracking branch 'upstream/main' into new-editors-main

# Conflicts:
# commandline/commands/Compiler.hx
# source/funkin/backend/FunkinSprite.hx
# source/funkin/backend/MusicBeatState.hx
# source/funkin/backend/MusicBeatSubstate.hx
# source/funkin/backend/chart/Chart.hx
# source/funkin/backend/chart/EventsData.hx
# source/funkin/backend/scripting/GlobalScript.hx
# source/funkin/backend/system/Controls.hx
# source/funkin/backend/system/MainState.hx
# source/funkin/backend/utils/AudioAnalyzer.hx
# source/funkin/backend/utils/CoolUtil.hx
# source/funkin/editors/character/CharacterInfoScreen.hx
# source/funkin/editors/charter/Charter.hx
# source/funkin/editors/charter/CharterSelection.hx
# source/funkin/editors/charter/CharterStrumline.hx
# source/funkin/editors/charter/CharterStrumlineButton.hx
# source/funkin/editors/charter/SongCreationScreen.hx
# source/funkin/editors/ui/UIDropDown.hx
# source/funkin/editors/ui/UIFileExplorer.hx
# source/funkin/game/Note.hx
# source/funkin/game/PlayState.hx
# source/funkin/game/SplashGroup.hx
# source/funkin/game/Stage.hx
# source/funkin/game/Strum.hx
# source/funkin/menus/FreeplayState.hx
# source/funkin/options/Options.hx
# source/funkin/options/keybinds/KeybindsOptions.hx
This commit is contained in:
Ralty
2025-07-19 00:57:08 +07:00
372 changed files with 3244 additions and 1248 deletions
+12 -12
View File
@@ -21,21 +21,21 @@ class AudioAnalyzer {
}
public function analyze(startPos:Float, endPos:Float):Float {
var bitsPerSample = buffer.bitsPerSample;
var multi = __timeMulti / 4000 / bitsPerSample;
var bytesStartPos:Int = Math.floor(startPos * multi) * bitsPerSample;
var bytesEndPos:Int = Math.floor(endPos * multi) * bitsPerSample;
var bytesStartPos:Int = Math.floor(startPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample;
var bytesEndPos:Int = Math.floor(endPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample;
bytesStartPos -= bytesStartPos % bitsPerSample;
bytesEndPos -= bytesEndPos % bitsPerSample;
var dataBuffer = buffer.data.buffer;
bytesStartPos -= bytesStartPos % buffer.bitsPerSample;
bytesEndPos -= bytesEndPos % buffer.bitsPerSample;
var maxByte:Int = 0;
for(i in 0...Math.floor((bytesEndPos - bytesStartPos) / bitsPerSample)) {
var bytePos = bytesStartPos + (i * bitsPerSample);
// What if bitsPerSample is 8? or 32? or 64? -Ne_Eo
var byte:Int = dataBuffer.get(bytePos) | (dataBuffer.get(bytePos + 1) << 8);
for(i in 0...Math.floor((bytesEndPos - bytesStartPos) / buffer.bitsPerSample)) {
#if js
var byte:Int = buffer.data[bytesStartPos + (i * buffer.bitsPerSample)]
| (buffer.data[bytesStartPos + (i * buffer.bitsPerSample) + 1] << 8);
#else
var byte:Int = buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample))
| (buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample) + 1) << 8);
#end
if (byte > 256 * 128) byte -= 256 * 256;
if (maxByte < byte) maxByte = byte;
}
+129 -7
View File
@@ -145,7 +145,7 @@ final class CoolUtil
if(result.isNothing) Logs.trace('The file where it has been tried to get the attributes from, might be corrupted or inexistent (code: ${result.getValue()})', WARNING);
return result;
#else
return 0;
return new FileAttributeWrapper(0);
#end
}
@@ -183,7 +183,7 @@ final class CoolUtil
addMissingFolders(Path.directory(path));
var result = NativeAPI.addFileAttributes(path, attrib, useAbsolute);
if(result == 0) Logs.trace('Failed to add attributes to $path with a code of: $result', WARNING);
if (result == 0) Logs.trace('Failed to add attributes to $path with a code of: $result', WARNING);
return result;
#else
return 0;
@@ -453,9 +453,13 @@ final class CoolUtil
if (Assets.exists(infoPath)) {
var musicInfo = IniUtil.parseAsset(infoPath, [
"BPM" => null,
"TimeSignature" => Flags.DEFAULT_BEATS_PER_MEASURE + "/" + Flags.DEFAULT_STEPS_PER_BEAT
"TimeSignature" => Flags.DEFAULT_BEATS_PER_MEASURE + "/" + Flags.DEFAULT_STEPS_PER_BEAT,
"LoopTime" => '${Flags.DEFAULT_LOOP_TIME}'
]);
if (FlxG.sound.music != null)
FlxG.sound.music.loopTime = Std.parseFloat(musicInfo["LoopTime"]) * 1000;
var timeSignParsed:Array<Null<Float>> = musicInfo["TimeSignature"] == null ? [] : [for(s in musicInfo["TimeSignature"].split("/")) Std.parseFloat(s)];
var beatsPerMeasure:Float = Flags.DEFAULT_BEATS_PER_MEASURE;
var stepsPerBeat:Int = Flags.DEFAULT_STEPS_PER_BEAT;
@@ -712,11 +716,11 @@ final class CoolUtil
* @param spr Sprite to load the graphic for
* @param path Path to the graphic
*/
public static function loadAnimatedGraphic(spr:FlxSprite, path:String) {
public static function loadAnimatedGraphic(spr:FlxSprite, path:String, fps:Float = 24.0) {
spr.frames = Paths.getFrames(path, true);
if (spr.frames != null && spr.frames.frames != null) {
spr.animation.add("idle", [for(i in 0...spr.frames.frames.length) i], 24, true);
spr.animation.add("idle", [for(i in 0...spr.frames.frames.length) i], fps, true);
spr.animation.play("idle");
}
@@ -915,8 +919,8 @@ final class CoolUtil
* @param mainEase Main ease
* @param suffix Suffix (Ignored if `mainEase` is `linear`)
*/
@:noUsing public static inline function flxeaseFromString(mainEase:String, suffix:String)
return Reflect.field(FlxEase, mainEase + (mainEase == "linear" ? "" : suffix));
@:noUsing public static inline function flxeaseFromString(mainEase:String, ?suffix:String)
return Reflect.field(FlxEase, mainEase + (mainEase == "linear" || suffix == null ? "" : suffix));
/*
* Returns the filename of a path, without the extension.
@@ -1323,6 +1327,124 @@ final class CoolUtil
public static inline function isMapEmpty<K, V>(map: Map<K, V>): Bool {
return !map.keys().hasNext();
}
public inline static function parsePropertyString(fieldPath:String):Array<OneOfTwo<String, Int>> {
return FlxTween.parseFieldString(fieldPath);
}
public static function stringifyFieldsPath(fields:Array<OneOfTwo<String, Int>>):String {
var str = new StringBuf();
var first = true;
for (field in fields) {
if (Type.typeof(field) == TInt) {
str.add('[${field}]');
} else {
if (!first)
str.add('.');
str.add(field);
}
first = false;
}
return str.toString();
}
public static function parseProperty(target:Dynamic, fields:OneOfTwo<String, Array<OneOfTwo<String, Int>>>):Dynamic {
var fields:Array<OneOfTwo<String, Int>> = {
if((fields is String)) CoolUtil.parsePropertyString(fields);
else fields;
}
var field = CoolUtil.last(fields);
for (i in 0...fields.length - 1) {
var component = fields[i];
if (Type.typeof(component) == TInt) {
if ((target is Array)) {
var index:Int = cast component;
var arr:Array<Dynamic> = cast target;
target = arr[index];
}
} else { // TClass(String)
target = Reflect.getProperty(target, component);
}
if (!Reflect.isObject(target) && !(target is Array))
throw 'The object does not have the property "$component" in "${stringifyFieldsPath(fields)}"';
}
return new PropertyInfo(target, field);
}
public static function cloneProperty(toTarget:Dynamic, fields:OneOfTwo<String, Array<OneOfTwo<String, Int>>>, fromTarget:Dynamic):Dynamic {
var fields:Array<OneOfTwo<String, Int>> = {
if((fields is String)) CoolUtil.parsePropertyString(fields);
else fields;
}
var toProperty = CoolUtil.parseProperty(toTarget, fields);
var fromProperty = CoolUtil.parseProperty(fromTarget, fields);
return toProperty.setValue(fromProperty.getValue());
}
}
class PropertyInfo {
public var object:Dynamic;
public var field:OneOfTwo<String, Int>;
public var typeOfField:Type.ValueType;
#if hscript_improved
public var isCustom:Bool = false;
public var custom:hscript.IHScriptCustomBehaviour;
#end
public function new(object:Dynamic, field:OneOfTwo<String, Int>) {
this.object = object;
this.field = field;
#if hscript_improved
if (object is hscript.IHScriptCustomBehaviour)
{
isCustom = true;
custom = cast object;
}
#end
typeOfField = Type.typeof(field);
}
public function getValue():Dynamic
{
if (typeOfField == TInt)
{
var index:Int = cast field;
var arr:Array<Dynamic> = cast object;
return arr[index];
}
else
{
#if hscript_improved
if (isCustom)
return custom.hget(field);
else
#end
return Reflect.getProperty(object, field);
}
}
public function setValue(value:Dynamic):Void
{
if (typeOfField == TInt)
{
var index:Int = cast field;
var arr:Array<Dynamic> = cast object;
arr[index] = value;
}
else
{
#if hscript_improved
if (isCustom)
custom.hset(field, value);
else
#end
Reflect.setProperty(object, field, value);
}
}
}
/**
+3 -3
View File
@@ -127,7 +127,7 @@ final class MemoryUtil {
public static function getMemType():String {
#if windows
var memoryMap:Map<Int, String> = [
0 => "Unknown",
0 => null,
1 => "Other",
2 => "DRAM",
3 => "Synchronous DRAM",
@@ -166,9 +166,9 @@ final class MemoryUtil {
];
var memoryOutput:Int = -1;
var process = new HiddenProcess("wmic", ["memorychip", "get", "SMBIOSMemoryType"]);
var process = new HiddenProcess("powershell", ["-Command", "Get-CimInstance Win32_PhysicalMemory | Select-Object -ExpandProperty SMBIOSMemoryType" ]);
if (process.exitCode() == 0) memoryOutput = Std.int(Std.parseFloat(process.stdout.readAll().toString().trim().split("\n")[1]));
if (memoryOutput != -1) return memoryMap[memoryOutput];
if (memoryOutput != -1) return memoryMap[memoryOutput] == null ? 'Unknown ($memoryOutput)' : memoryMap[memoryOutput];
#elseif mac
var process = new HiddenProcess("system_profiler", ["SPMemoryDataType"]);
var reg = ~/Type: (.+)/;
+120
View File
@@ -0,0 +1,120 @@
package funkin.backend.utils;
enum abstract RegistryHive(Int) {
var HKEY_CLASSES_ROOT = 0x80000000;
var HKEY_CURRENT_USER = 0x80000001;
var HKEY_LOCAL_MACHINE = 0x80000002;
var HKEY_USERS = 0x80000003;
var HKEY_CURRENT_CONFIG = 0x80000005;
}
#if windows
@:cppFileCode('
#include <windows.h>
#include <tchar.h>
#include <string>
#include <vector>
')
#end
class RegistryUtil {
#if windows
@:functionCode('
HKEY hKey;
LONG result;
DWORD dataSize = 0;
DWORD dataType = 0;
std::wstring subkey = std::wstring(key.wchar_str());
std::wstring valname = std::wstring(string.wchar_str());
result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_READ, &hKey);
if (result != ERROR_SUCCESS) return null();
result = RegQueryValueExW(hKey, valname.c_str(), NULL, &dataType, NULL, &dataSize);
if (result != ERROR_SUCCESS || dataSize == 0) {
RegCloseKey(hKey);
return null();
}
std::vector<wchar_t> buffer(dataSize / sizeof(wchar_t));
result = RegQueryValueExW(hKey, valname.c_str(), NULL, NULL, (LPBYTE)buffer.data(), &dataSize);
RegCloseKey(hKey);
if (result == ERROR_SUCCESS) {
return ::String(buffer.data());
}
return null();
')
#end
public static function get(hive:RegistryHive, key:String, string:String):Null<String>
{
return null;
}
#if windows
@:functionCode('
HKEY hKey;
LONG result;
std::wstring subkey = std::wstring(key.wchar_str());
std::wstring valname = std::wstring(string.wchar_str());
std::wstring data = std::wstring(value.wchar_str());
result = RegCreateKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL);
if (result != ERROR_SUCCESS) return false;
result = RegSetValueExW(hKey, valname.c_str(), 0, REG_SZ, (const BYTE*)data.c_str(), (DWORD)((data.length() + 1) * sizeof(wchar_t)));
RegCloseKey(hKey);
return result == ERROR_SUCCESS;
')
#end
public static function set(hive:RegistryHive, key:String, string:String, value:String):Bool
{
return false;
}
#if windows
@:functionCode('
HKEY hKey;
LONG result;
std::wstring subkey = std::wstring(key.wchar_str());
std::wstring valname = std::wstring(string.wchar_str());
result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_READ, &hKey);
if (result != ERROR_SUCCESS) return false;
DWORD dataType = 0;
result = RegQueryValueExW(hKey, valname.c_str(), NULL, &dataType, NULL, NULL);
RegCloseKey(hKey);
return result == ERROR_SUCCESS;
')
#end
public static function exists(hive:RegistryHive, key:String, string:String):Bool
{
return false;
}
#if windows
@:functionCode('
HKEY hKey;
LONG result;
std::wstring subkey = std::wstring(key.wchar_str());
std::wstring valname = std::wstring(string.wchar_str());
result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_SET_VALUE, &hKey);
if (result != ERROR_SUCCESS) return false;
result = RegDeleteValueW(hKey, valname.c_str());
RegCloseKey(hKey);
return result == ERROR_SUCCESS;
')
#end
public static function delete(hive:RegistryHive, key:String, string:String):Bool {
return false;
}
}
+5 -4
View File
@@ -114,12 +114,13 @@ final class XMLUtil {
* @param parentFolder The parent folder
* @param defaultAnimType The default animation type
*/
public static function loadSpriteFromXML(spr:FunkinSprite, node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT):FunkinSprite {
public static function loadSpriteFromXML(spr:FunkinSprite, node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, loadGraphic:Bool = true):FunkinSprite {
if (parentFolder == null) parentFolder = "";
spr.name = node.getAtt("name");
spr.antialiasing = true;
spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite").getDefault(spr.name)}', null, true));
if (loadGraphic)
spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite").getDefault(spr.name)}', null, true));
spr.spriteAnimType = defaultAnimType;
if (node.has.type) {
@@ -239,10 +240,10 @@ final class XMLUtil {
* @param cl The class to create (advanced)
* @param args The arguments to pass to the class (advanced)
*/
public static inline function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>, ?args:Array<Dynamic>):FunkinSprite {
public static inline function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>, ?args:Array<Dynamic>, loadGraphic:Bool = true):FunkinSprite {
if(cl == null) cl = FunkinSprite;
if(args == null) args = [];
return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType);
return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType, loadGraphic);
}
/**
+22 -21
View File
@@ -1,5 +1,8 @@
package funkin.backend.utils;
import haxe.zip.Entry;
import haxe.zip.Reader;
#if sys
#if (!macro && sys)
import openfl.display.BitmapData;
@@ -9,8 +12,6 @@ import haxe.Exception;
import haxe.Json;
import haxe.crypto.Crc32;
import haxe.zip.Compress;
import haxe.zip.Entry;
import haxe.zip.Reader;
import haxe.zip.Tools;
import haxe.zip.Uncompress;
import haxe.zip.Writer;
@@ -135,12 +136,12 @@ final class ZipUtil {
if (prog == null) prog = new ZipProgress();
try {
var curPath:Array<String> = ['$path'];
var curPath:Array<String> = [path];
var destPath:Array<String> = [];
if (prefix != "") {
prefix = prefix.replace("\\", "/");
while(prefix.charAt(0) == "/") prefix = prefix.substr(1);
while(prefix.charAt(prefix.length-1) == "/") prefix = prefix.substr(0, prefix.length-1);
while(prefix.charCodeAt(0) == "/".code) prefix = prefix.substr(1);
while(prefix.charCodeAt(prefix.length-1) == "/".code) prefix = prefix.substr(0, prefix.length-1);
destPath.push(prefix);
}
@@ -160,7 +161,7 @@ final class ZipUtil {
} else {
// is file, put it in the list
var zipPath = '$zipPath/$e';
while(zipPath.charAt(0) == "/") zipPath = zipPath.substr(1);
while(zipPath.charCodeAt(0) == "/".code) zipPath = zipPath.substr(1);
files.push(new StrNameLabel('$path/$e', zipPath));
}
}
@@ -227,20 +228,6 @@ class ZipProgress {
public function new() {}
}
class ZipReader extends Reader {
public var files:List<Entry>;
public override function read() {
if (files != null) return files;
try {
var files = super.read();
return this.files = files;
} catch(e) {
}
return new List<Entry>();
}
}
class ZipWriter extends Writer {
public function flush() {
o.flush();
@@ -265,4 +252,18 @@ class StrNameLabel {
this.label = label;
}
}
#end
#end
class ZipReader extends Reader {
public var files:List<Entry>;
public override function read() {
if (files != null) return files;
try {
var files = super.read();
return this.files = files;
} catch(e) {
}
return new List<Entry>();
}
}