move some scripts from art/ submodule to main funkin repo

This commit is contained in:
Cameron Taylor
2026-01-29 22:07:57 -07:00
committed by Hundrec
parent 430e2b9dcc
commit 899d793e1c
18 changed files with 914 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
package art;
using DateTools;
class Postbuild
{
static function main()
{
/* trace('creating NG zip');
var binFolder:String = 'export/release/html5/bin/';
var date = Date.now();
var outputName:String = 'NG-' + date.format("%Y%m%d--%H%M") + '.zip';
var out = sys.io.File.write(outputName);
var zip = new haxe.zip.Writer(out);
zip.write(getEntries(binFolder));
trace('Finished creating ZIP');
*/
}
static function getEntries(dir:String, entries:List<haxe.zip.Entry> = null, inDir:Null<String> = null)
{
if (entries == null) entries = new List<haxe.zip.Entry>();
if (inDir == null) inDir = dir;
for (file in sys.FileSystem.readDirectory(dir))
{
var path = haxe.io.Path.join([dir, file]);
if (sys.FileSystem.isDirectory(path)) getEntries(path, entries, inDir);
else
{
var bytes:haxe.io.Bytes = haxe.io.Bytes.ofData(sys.io.File.getBytes(path).getData());
var entry:haxe.zip.Entry =
{
fileName: StringTools.replace(path, inDir, ""),
fileSize: bytes.length,
fileTime: Date.now(),
compressed: false,
dataSize: sys.FileSystem.stat(path).size,
data: bytes,
crc32: haxe.crypto.Crc32.make(bytes)
};
entries.push(entry);
}
}
return entries;
}
}
+180
View File
@@ -0,0 +1,180 @@
import haxe.Json;
import haxe.Log;
import haxe.macro.Compiler;
import sys.FileSystem;
import sys.io.File;
using StringTools;
#if !interp
package art;
// needs this thing to be run by interp, just for me lol!
#end
// general use of this!!
// if run in INTERP MODE:
// - art>haxe --main SongConverter --interp
// - song to convert: ../assets/preload/data/
// it will run through EVERY folder in there and convert the old songs to the new format!
// TODO make instructions for the C# script version!
class SongConverter
{
public static var VERSION:String = '0.1.0';
// TODO
// Update engine shit, to accomodate single file
static function main()
{
// trace(Compiler.get);
// trace(Compiler.getDefine('sysfsd'));
Sys.stdout().writeString('Please type directory you want to convert: ');
Sys.stdout().flush();
final input = Sys.stdin().readLine();
trace('Hello ${input}!');
/* Sys.stdout().writeString('What type of conversion do you need?\n\t0: Old charts to new chart\n\t1: new chart to .... new chart? lol\n>');
Sys.stdout().flush();
final deleteShitIDK = Std.parseInt(Sys.stdin().readLine());
trace(deleteShitIDK); */
var songCount:Int = 0;
for (fileThing in FileSystem.readDirectory('${input}/.'))
{
// trace(fileThing);
// trace(FileSystem.isDirectory(${input} + "/" + fileThing));
if (FileSystem.isDirectory(${input} + "/" + fileThing) && fileThing.toLowerCase() != 'smash')
{
trace('Formatting $fileThing');
formatSongs(fileThing, input);
songCount++;
}
}
trace('CONVERTED $songCount SONGS!!');
var timer:Int = 5;
while (timer > 0)
{
Sys.stdout().writeString('CLOSING IN $timer SECONDS!');
Sys.sleep(1);
Sys.print("\r");
timer -= 1;
}
}
public static function formatSongs(songName:String, root:String)
{
var existsArray:Array<Bool> = [];
var songMap:Map<String, Dynamic> = new Map();
var speedMap:Map<String, Dynamic> = new Map();
for (songFile in FileSystem.readDirectory('$root/$songName/'))
{
if (!songFile.endsWith('json') || !songFile.startsWith(songName)) continue; // ACCOMODATE FOR PICO SPEAKER EVENTUALLY!
var funnyFile:Dynamic = cast Json.parse(File.getContent('$root/$songName/$songFile'));
var songSplit:Array<String> = songFile.split('-');
var songShit:String = 'normal';
if (songSplit.length > 1)
{
if (songSplit[1] == 'new.json')
{
FileSystem.deleteFile('$root/$songName/$songFile');
trace('DELETEING $songFile');
continue;
}
songShit = songSplit[1].substr(0, songSplit[1].length - 5);
}
if (songShit != 'normal')
{
songMap[songShit] = funnyFile.song.notes;
speedMap[songShit] = funnyFile.song.speed;
}
else
{
//
if (funnyFile.song.notes is Array)
{
songMap[songShit] = funnyFile.song.notes[1];
speedMap[songShit] = funnyFile.song.speed[1];
}
else if (Type.typeof(funnyFile.song.notes) == TObject)
{
var dipshitMap:Map<String, Dynamic> = cast funnyFile.song.notes;
// songMap[songShit] = dipshitMap.get('normal');
}
else
{
songMap[songShit] = funnyFile.song.notes;
speedMap[songShit] = funnyFile.song.speed;
}
}
}
var fileNormal:Dynamic = cast Json.parse(File.getContent('$root/$songName/$songName.json'));
fileNormal.song.notes = songMap;
fileNormal.song.speed = speedMap;
fileNormal.song.hasDialogueFile = false;
fileNormal.song.stageDefault = getStage(songName);
fileNormal.version = 'FNF SongConverter $VERSION';
var daJson = Json.stringify(fileNormal, null, "\t");
// trace(daJson);
File.saveContent('$root/$songName/$songName.json', daJson);
}
static function noteCleaner(notes:Array<Dynamic>):Array<Dynamic>
{
var swagArray:Array<Dynamic> = [];
for (i in notes)
{
if (i.sectionNotes.length > 0) swagArray.push(i);
}
return swagArray;
}
/**
* Quicky lil function just for me formatting the old songs hehehe
*/
static function getStage(songName:String):String
{
switch (songName)
{
case 'spookeez' | 'monster' | 'south':
return "spooky";
case 'pico' | 'blammed' | 'philly':
return 'philly';
case "milf" | 'satin-panties' | 'high':
return 'limo';
case "cocoa" | 'eggnog':
return 'mall';
case 'winter-horrorland':
return 'mallEvil';
case 'senpai' | 'roses':
return 'school';
case 'thorns':
return 'schoolEvil';
case 'guns' | 'stress' | 'ugh':
return 'tank';
default:
return 'stage';
}
}
}
+99
View File
@@ -0,0 +1,99 @@
import csv
import re
import sys
import json
import subprocess
# This script imports a CSV file, and bulk updates the chart metadata files
#
# Requirements:
# python3 I believe
# and you need to have npm/npx, as we use that to run prettier to keep the same formatting
#
# Usage:
# In our Google Sheets, export as .csv file
#
# Then in here from within this directory, run:
# python3 sheetsToCharts.py path/to/csv/file.csv
#
# use from within this directory, as the paths to the chart metadata files are relative to this directory
# Get the command line argument, if one wasn't already provided
if len(sys.argv) < 2:
csv_file = input("Please provide the CSV file path: ")
else:
csv_file = sys.argv[1]
# Open the CSV file
with open(csv_file, 'r') as file:
reader = csv.reader(file)
# Which rows for each difficulty are we looking for
diff_rows = {}
# Iterate over each row in the CSV file
for idx, row in enumerate(reader):
# The first row has headers for the columns, stuff like "Easy - Difficulty", so we want
# to know which rows corresponde to which difficulty
if idx == 0:
pattern = r"\b(?:difficulty)\b"
diff_cols = []
diff_words = []
# Go through each column, and see if it includes the word "difficulty"
# then append the column index, and the first word of the column (usually what difficulty it is, "easy", "normal", "hard", etc.)
for colindex, column in enumerate(row):
txt = re.findall(pattern, column, flags=re.IGNORECASE)
if txt:
diff_cols.append(colindex)
diff_words.append(column.split()[0].lower())
# We then create a dictionary for use later, where the key is the difficulty, and the value is the column index
diff_rows = dict(zip(diff_words, diff_cols))
else:
# Some song title parsing, to match the filenames we used in for the chart files
song_title = row[1].lower() # we use lowercase for filenames, SOUTH -> south
song_title = song_title.replace(" ", "-") # usually we change spaces to dashes, PHILLY NICE -> philly-nice
song_title = song_title.replace(".", "") # M.I.L.F. -> milf
song_title = song_title.replace("'", "") # blazin' -> blazin
# we open the chart metadata json file for writing and reading ("r+")
with open(f"../../../assets/preload/data/songs/{song_title}/{song_title}-metadata.json", "r+") as chart_metadata_file:
json_data = json.load(chart_metadata_file) # load the file as a python data structure/dict
play_data = json_data["playData"] # we want to modify the "playData" section, as that holds the difficulty
# note to self, python equals(=) operator seems to create a reference for the variable,
# so modifying play_data will also modify json_data, so we can save json_data easily later
# if the chart metadata file doesn't already have a "ratings" dict/section, we create one here with 0 for each
if "ratings" not in play_data:
play_data["ratings"] = {'easy': 0, 'normal': 0, 'hard': 0}
ratings = play_data["ratings"]
# Now we go through our data from the csv file, and the data we kept from the columns there
# will be put into our new ratings var, if it exists
for diff, col in diff_rows.items():
if row[col] == "":
continue
if diff in ratings:
ratings[diff] = round(float(row[col])) # convert the string to a float, and then round it to nearest int
# Convert the python json_data dict back to a json string
json_output = json.dumps(json_data)
# Write the json string back to the file, and truncate the rest of the file
chart_metadata_file.seek(0)
chart_metadata_file.write(json_output)
chart_metadata_file.truncate()
chart_metadata_file.close()
# Bit hacky, but we simply run `npx prettier` using the same rules we use for FNF here
# essnetially running it via cli and passing in the songs folder and our prettier config file
# This should make the spacing and formatting consistent
command = "npx prettier ../../../assets/preload/data/songs --write --config ../../../.prettierrc.js"
subprocess.run(command, shell=True)
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
# Find all files in OGG format and execute ffmpeg to convert them to MP3
# Skips files that are already in MP3 format
find "../assets/" -name "*.ogg" -exec sh -c 'ffmpeg -n -i {} "${0%.*}.mp3"' {} \;
+218
View File
@@ -0,0 +1,218 @@
import requests
import sys
PROJECT_ID = 5582053
TEMPLATE = 'jQuery("#new-event-name").val("{event}"); jQuery("#save-new-event").click();'
EVENT_TEMPLATES = [
"start-game",
"watch-cartoon",
"open-credits",
"start-song_{songId}-{variation}",
"blueballs_{songId}-{variation}",
"complete-song_{songId}-{variation}",
"start-level_{levelId}",
"complete-level_{levelId}",
"earn-rank_{rankName}",
]
SONG_IDS = [
{ "songId": "tutorial", "variation": "default" },
{ "songId": "tutorial", "variation": "erect" },
{ "songId": "tutorial", "variation": "pico" },
{ "songId": "bopeebo", "variation": "default" },
{ "songId": "bopeebo", "variation": "erect" },
{ "songId": "bopeebo", "variation": "pico" },
{ "songId": "fresh", "variation": "default" },
{ "songId": "fresh", "variation": "erect" },
{ "songId": "fresh", "variation": "pico" },
{ "songId": "dadbattle", "variation": "default" },
{ "songId": "dadbattle", "variation": "erect" },
{ "songId": "dadbattle", "variation": "pico" },
{ "songId": "spookeez", "variation": "default" },
{ "songId": "spookeez", "variation": "erect" },
{ "songId": "spookeez", "variation": "pico" },
{ "songId": "south", "variation": "default" },
{ "songId": "south", "variation": "erect" },
{ "songId": "south", "variation": "pico" },
{ "songId": "monster", "variation": "default" },
{ "songId": "monster", "variation": "erect" },
{ "songId": "monster", "variation": "pico" },
{ "songId": "pico", "variation": "default" },
{ "songId": "pico", "variation": "erect" },
{ "songId": "pico", "variation": "pico" },
{ "songId": "philly-nice", "variation": "default" },
{ "songId": "philly-nice", "variation": "erect" },
{ "songId": "philly-nice", "variation": "pico" },
{ "songId": "blammed", "variation": "default" },
{ "songId": "blammed", "variation": "erect" },
{ "songId": "blammed", "variation": "pico" },
{ "songId": "satin-panties", "variation": "default" },
{ "songId": "satin-panties", "variation": "erect" },
{ "songId": "satin-panties", "variation": "pico" },
{ "songId": "high", "variation": "default" },
{ "songId": "high", "variation": "erect" },
{ "songId": "high", "variation": "pico" },
{ "songId": "milf", "variation": "default" },
{ "songId": "milf", "variation": "erect" },
{ "songId": "milf", "variation": "pico" },
{ "songId": "cocoa", "variation": "default" },
{ "songId": "cocoa", "variation": "erect" },
{ "songId": "cocoa", "variation": "pico" },
{ "songId": "eggnog", "variation": "default" },
{ "songId": "eggnog", "variation": "erect" },
{ "songId": "eggnog", "variation": "pico" },
{ "songId": "winter-horrorland", "variation": "default" },
{ "songId": "winter-horrorland", "variation": "erect" },
{ "songId": "winter-horrorland", "variation": "pico" },
{ "songId": "senpai", "variation": "default" },
{ "songId": "senpai", "variation": "erect" },
{ "songId": "senpai", "variation": "pico" },
{ "songId": "roses", "variation": "default" },
{ "songId": "roses", "variation": "erect" },
{ "songId": "roses", "variation": "pico" },
{ "songId": "thorns", "variation": "default" },
{ "songId": "thorns", "variation": "erect" },
{ "songId": "thorns", "variation": "pico" },
{ "songId": "ugh", "variation": "default" },
{ "songId": "ugh", "variation": "erect" },
{ "songId": "ugh", "variation": "pico" },
{ "songId": "guns", "variation": "default" },
{ "songId": "guns", "variation": "erect" },
{ "songId": "guns", "variation": "pico" },
{ "songId": "stress", "variation": "default" },
{ "songId": "stress", "variation": "erect" },
{ "songId": "stress", "variation": "pico" },
{ "songId": "darnell", "variation": "default" },
{ "songId": "darnell", "variation": "erect" },
{ "songId": "darnell", "variation": "bf" },
{ "songId": "lit-up", "variation": "default" },
{ "songId": "lit-up", "variation": "erect" },
{ "songId": "lit-up", "variation": "bf" },
{ "songId": "2hot", "variation": "default" },
{ "songId": "2hot", "variation": "erect" },
{ "songId": "2hot", "variation": "bf" },
{ "songId": "blazin", "variation": "default" },
{ "songId": "blazin", "variation": "erect" },
{ "songId": "blazin", "variation": "bf" },
]
LEVEL_IDS = [
"tutorial",
"week1",
"week2",
"week3",
"week4",
"week5",
"week6",
"week7",
"weekend1",
"week8",
]
RANKS = [
"PERFECT_GOLD",
"PERFECT",
"EXCELLENT",
"GREAT",
"GOOD",
"SHIT",
]
def formatEventSong(event):
result = []
for song in SONG_IDS:
result.append(event.format(
songId = song["songId"],
variation = song["variation"]
))
return result
def formatEventLevel(event):
result = []
for level in LEVEL_IDS:
result.append(event.format(
levelId = level
))
return result
def formatEventRank(event):
result = []
for rank in RANKS:
result.append(event.format(
rankName = rank
))
return result
def formatEvent(event):
if "{songId}" in event or "{variation}" in event:
return formatEventSong(event)
if "{levelId}" in event:
return formatEventLevel(event)
if "{rankName}" in event:
return formatEventRank(event)
return [event]
def createEvent(event):
print(TEMPLATE.format(event=event))
def main():
event_list = []
for event in EVENT_TEMPLATES:
event_list.extend(formatEvent(event))
# Filter out duplicates then sort alphabetically
event_list = list(set(event_list))
event_list.sort()
for event in event_list:
createEvent(event)
if __name__ == "__main__":
main()
#function sleep(ms) {
# console.log("sleeping for " + ms + "ms");
# return new Promise(resolve => setTimeout(resolve, ms));
#}
#async function example() {
# jQuery("#new-event-name").val("blueballs_blazin-erect"); jQuery("#save-new-event").click();
# await sleep(1000);
# jQuery("#new-event-name").val("blueballs_blazin-erect"); jQuery("#save-new-event").click();
# await sleep(1000);
#}
#example();
@@ -0,0 +1,11 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build html5 -final
echo UPLOADING TO ITCH
butler push ./export/release/html5/bin ninja-muffin24/funkin-secret:html5
butler status ninja-muffin24/funkin-secret:html5
echo ITCH SHIT UPDATED LMAOOOOO
pause
+11
View File
@@ -0,0 +1,11 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build html5 -debug
echo UPLOADING TO ITCH
butler push ./export/debug/html5/bin ninja-muffin24/funkin-secret:html5
butler status ninja-muffin24/funkin-secret:html5
echo ITCH SHIT UPDATED LMAOOOOO
pause
@@ -0,0 +1,15 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build windows -final -clean
echo UPLOADING 64 BIT VERSION TO ITCH
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-64bit
lime build windows -final -32
echo UPLOADING 32 BIT VERSION TO ITCH
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-32bit
butler status ninja-muffin24/funkin:windows-32bit
butler status ninja-muffin24/funkin:windows-64bit
echo ITCH SHIT UPDATED LMAOOOOO
pause
@@ -0,0 +1,11 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build windows -debug
echo UPLOADING 64 BIT VERSION TO ITCH
butler push ./export/debug/windows/bin ninja-muffin24/funkin:windows-secretBeta
butler status ninja-muffin24/funkin:windows-secretBeta
echo ITCH SHIT UPDATED LMAOOOOO
pause
@@ -0,0 +1,15 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build windows -release
echo UPLOADING 64 BIT VERSION TO ITCH
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-64bit
lime build windows -release -32
echo UPLOADING 32 BIT VERSION TO ITCH
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-32bit
butler status ninja-muffin24/funkin:windows-32bit
butler status ninja-muffin24/funkin:windows-64bit
echo ITCH SHIT UPDATED LMAOOOOO
pause
@@ -0,0 +1,12 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build html5 -final -Dng
echo ZIPPING
cd export
7z a -tzip -r swag release\html5\bin
start .
start https://www.newgrounds.com/projects/games/1528775
pause
@@ -0,0 +1,27 @@
@echo off
cd ..
@echo on
echo REBUILDING LIME FOR EXPORT (VERBOSE)
@echo off
color 0b
@echo on
lime rebuild windows -clean -v
@echo off
color 0a
@echo on
lime rebuild switch -clean -v
@echo off
color 0e
@echo on
lime rebuild tools -clean -v
echo HOPE AND PRAY...
@echo off
color 0a
@echo on
echo BUILDING GAME
lime build switch -final -clean -v
@echo off
color 0b
@echo on
echo NSP FILE CREATED
pause
+110
View File
@@ -0,0 +1,110 @@
// Highlight keyframes in flash
// drag .jsfl script in
// will export the layer info of the first(?) element it finds?
// maybe someone can extend this to add more depth / functionality!
// see FramesJSFLParser.hx and CharSelectGF.hx for sample implementation
function splitArrayIntoChunks(array, chunkSize) {
var result = [];
for (var i = 0; i < array.length; i += chunkSize) {
var chunk = array.slice(i, i + chunkSize);
result.push(chunk);
}
return result;
}
var isJson = false;
function addField(name, variable, end)
{
return (isJson) ? addFJson(name, variable, end) : addF(variable, end);
}
function addFJson(name, variable, end) {
return "\"" + name + "\": " + variable + ((!end) ? "," : "");
}
function addF(variable, end) {
return variable + ((end)? "" : " ");
}
fl.outputPanel.clear();
var timeline = fl.getDocumentDOM().getTimeline();
var daSelection = timeline.getSelectedFrames();
var splitArrays = splitArrayIntoChunks(daSelection, 3);
var uri = undefined;
if (fl.version.substring(4).split(",")[0] < 13)
{
var macFormat = "Folder|TEXT[*.||";
var winFormat = "Folder|*.||";
var previewArea = {};
uri = fl.browseForFileURL("save", "where to save files pick folder...", previewArea, macFormat, winFormat);
}
else
uri = fl.browseForFileURL("save", "where to save files pick folder..", "Folder(*.)", "*.");
for (var i = 0; i < splitArrays.length; i+=1)
{
var curLayerInfo = splitArrays[i];
var curLayerInd = curLayerInfo[0];
var curLayer = fl.getDocumentDOM().getTimeline().layers[curLayerInd];
var layerURI = uri;
var fileName = "";
var uris = uri.split("/");
fileName = uris[uris.length - 1];
FLfile.createFolder(layerURI);
var fileData = "{\n";
if (!isJson)
fileData = "";
var selectedFrames = curLayer.frames;
var sliceLength = curLayerInfo[2] - curLayerInfo[1];
selectedFrames =selectedFrames.slice(curLayerInfo[1],curLayerInfo[2]);
if (isJson)
fileData += "\"frames\": [";
for (var frameInd = curLayerInfo[1]; frameInd < curLayerInfo[2]; frameInd+=1)
{
if (isJson)
fileData += "\n{"
var curFrame = curLayer.frames[frameInd];
if (curFrame.isEmpty)
{
continue;
}
for (var elementInd = 0; elementInd < curFrame.elements.length; elementInd+=1)
{
var curElement = curFrame.elements[elementInd];
fileData += addField("x", curElement.left, false);
fileData += addField("y", curElement.top, false);
fileData += addField("alpha", (curElement.colorAlphaPercent == undefined) ? 100 : curElement.colorAlphaPercent, false);
fileData += addField("scaleX", curElement.scaleX, false);
fileData += addField("scaleY", curElement.scaleY, true);
if (frameInd != curLayerInfo[2] - 1)
fileData += "\n";
}
if (isJson)
fileData += "},";
}
if (isJson)
fileData += "]";
if (isJson)
fileData += "\n}";
FLfile.write(layerURI + "/" + fileName + ".txt", fileData);
}
+6
View File
@@ -0,0 +1,6 @@
# README
jsfl is a scripting format with javascript syntax, I believe it's old js (no `let`, idk the spec!)
Official Adobe JSFL docuementation
[https://github.com/AdobeDocs/developers-animatesdk-docs/tree/master](https://github.com/AdobeDocs/developers-animatesdk-docs/tree/master)
+120
View File
@@ -0,0 +1,120 @@
#!/bin/bash
set -e # Exit immediately if a command exits with a non-zero status
echo "Note! This should be run from the root of the main Funkin repo, after creating a universal macOS build!"
echo "This script will assist in codesigning the Funkin.app for macOS"
echo ""
TEAM_ID="Z7G7AVNGSH"
ISSUER_ID="5205caca-2a9b-4b5e-9390-1db124cf51f4"
DEVELOPER_CERT_NAME="Developer ID Application: The Funkin' Crew Incorporated ($TEAM_ID)"
APP_PATH="export/release/macosUniversal/Funkin.app"
# Check if app exists
if [ ! -d "$APP_PATH" ]; then
echo "Error: $APP_PATH does not exist! Please build the app first."
exit 1
fi
echo "Preparing/Checking the AuthKey"
team_key_path=~/.appstoreconnect/private_keys/
# Check if directory exists, if not, create it
if [ ! -d "$team_key_path" ]; then
echo "The directory $team_key_path does not exist! Creating it now."
mkdir -p "$team_key_path"
echo ""
echo "$team_key_path created, put the private key file in there (should be called 'AuthKey_XXXXXXX.p8')"
echo "Ask Cameron for the private key file!"
echo ""
fi
# wait for the user to put the file in the directory, if it's not there
while [ ! -f "${team_key_path}"AuthKey_*.p8 ]; do
echo "Waiting for the private key file to be placed in $team_key_path... (press Ctrl+C to exit)"
sleep 5
done
key_id=$(ls "$team_key_path" | grep -oE '[A-Z0-9]{10}' | head -n 1)
if [ -z "$key_id" ]; then
echo "Error: Could not extract key ID from the AuthKey file name"
exit 1
fi
echo "Found AuthKey with key id: $key_id"
key_path="${team_key_path}AuthKey_${key_id}.p8"
cd export/release/macosUniversal || { echo "Failed to change directory"; exit 1; }
echo "Ensuring properly formatted Entitlements.plist"
if [ ! -f "Funkin.app/Contents/Entitlements.plist" ]; then
echo "Error: Entitlements.plist not found"
exit 1
fi
plutil -convert xml1 Funkin.app/Contents/Entitlements.plist
plutil -convert xml1 Funkin.app/Contents/Info.plist
plutil -lint Funkin.app/Contents/Entitlements.plist
plutil -lint Funkin.app/Contents/Info.plist
echo "Entitlements.plist and Info.plist should be properly formatted..."
echo "Codesigning - Funkin.app"
# Check the keychain for the funkin crew certificate
echo "Checking for The Funkin' Crew certificate in the keychain..."
if ! security find-identity -p codesigning | grep -q "The Funkin' Crew Incorporated"; then
echo "The Funkin' Crew Developer ID Application certificate was not found in the keychain!"
echo "Download the certificate from:"
echo "https://developer.apple.com/account/resources/certificates/download/Z62SXF29U7"
exit 1
fi
echo "Codesigning - Funkin.app"
codesign --force --deep --timestamp --sign "$DEVELOPER_CERT_NAME" --options runtime Funkin.app || {
echo "Code signing failed"
exit 1
}
echo "Codesigning - Funkin.app done!"
# Debug info to see if the app is properly signed
codesign -vv --deep --strict Funkin.app || {
echo "Code signing verification failed"
exit 1
}
echo ""
echo "Zipping Funkin.app for notarization (using ditto)..."
# Check if Funkin.app.zip already exists, if so, delete it
if [ -f Funkin.app.zip ]; then
echo "Funkin.app.zip already exists, deleting it..."
rm -rf Funkin.app.zip
fi
ditto -c -k --keepParent Funkin.app Funkin.app.zip || {
echo "Failed to create zip file"
exit 1
}
echo "Funkin.app zipped, now sending to Apple for notarization..."
xcrun notarytool submit Funkin.app.zip --issuer "$ISSUER_ID" --key-id "$key_id" --key "$key_path" --wait || {
echo "Notarization submission failed"
exit 1
}
# Clean up zip file
rm -rf Funkin.app.zip
echo "Stapling Funkin.app"
xcrun stapler staple Funkin.app || {
echo "Stapling failed"
exit 1
}
echo "Code sign information!"
codesign -dv --verbose=4 Funkin.app
echo "Notarization information (stapler):"
xcrun stapler validate Funkin.app || {
echo "Stapler validation failed"
exit 1
}
cd ../../.. || { echo "Failed to return to original directory"; exit 1; }
echo "Notarization complete! Funkin.app is now codesigned and notarized!"
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
lime build mac -64 -release
cd export/release
rm -rf macosUniversal
mkdir macosUniversal
cp -r macos/bin/Funkin.app macosUniversal
cd ../..
lime build mac -arm64 -release
cd export/release
cp -r macos/bin/Funkin.app/Contents/MacOS/Funkin macosUniversal/Funkin.app/Contents/MacOS/FunkinArm
cp -r macos/bin/Funkin.app/Contents/MacOS/lime.ndll macosUniversal/Funkin.app/Contents/MacOS/limeArm.ndll
cd macosUniversal/Funkin.app/Contents/MacOS
mv Funkin Funkin64
mv lime.ndll lime64.ndll
lipo -create -output lime.ndll lime64.ndll limeArm.ndll
lipo -create -output Funkin Funkin64 FunkinArm
rm FunkinArm Funkin64 lime64.ndll limeArm.ndll
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
# Find all files in PNG format and execute oxipng to optimize file sizes
cd ../assets/ && oxipng -o 6 --strip safe --alpha {} **/*.png
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
# Resize each health icon to 512x512
find "./discord/" -name "icon-*.png" -exec sh -c 'ffmpeg -i {} -vf scale=512:512 "${0%.*}2.png"' {} \;