From 9dfc56eb4a8bcc927ee48269eb691be07359529c Mon Sep 17 00:00:00 2001 From: Azure Date: Sun, 15 Mar 2026 18:36:05 +0100 Subject: [PATCH] First commit --- .css | 3 + .gitignore | 1 + bin/fennec.js | 321 ++++++++++++++++++++++++++++++++++++ build/metadata.smdh | Bin 0 -> 14016 bytes lib/transcompiler.js | 83 ++++++++++ package-lock.json | 28 ++++ package.json | 24 +++ runtime/Makefile | 39 +++++ runtime/gen/out.cpp | 29 ++++ runtime/lib/fennec_core.cpp | 64 +++++++ runtime/lib/fennec_core.h | 21 +++ source/main.js | 11 ++ 12 files changed, 624 insertions(+) create mode 100644 .css create mode 100644 .gitignore create mode 100644 bin/fennec.js create mode 100644 build/metadata.smdh create mode 100644 lib/transcompiler.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 runtime/Makefile create mode 100644 runtime/gen/out.cpp create mode 100644 runtime/lib/fennec_core.cpp create mode 100644 runtime/lib/fennec_core.h create mode 100644 source/main.js diff --git a/.css b/.css new file mode 100644 index 0000000..c33c38e --- /dev/null +++ b/.css @@ -0,0 +1,3 @@ +body { + font-style: ; +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07e6e47 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/bin/fennec.js b/bin/fennec.js new file mode 100644 index 0000000..c37bcc7 --- /dev/null +++ b/bin/fennec.js @@ -0,0 +1,321 @@ +const BackgroundTypes = { + IMAGE: 0, + SOLID: 1, + GRADIENT: 2 +} + +const BorderTypes = { + NONE: 0, + SOLID: 1 +} + +const Colors = { + WHITE: new N3DSColorRGBA(255, 255, 255, 1), + BLACK: new N3DSColorRGBA(0, 0, 0, 1) +} + +const FontStyles = { + NORMAL: 0, + ITALIC: 1, + OBLIQUE: 2 +} + +const Screens = { + TOP: 0, + BOTTOM: 1 +} + +const Languages = { + DEFAULT: "FR" +} + +class Fennec { + constructor() { + this.screens = { + Top: new N3DSScreen(Screens.TOP), + Bottom: new N3DSScreen(Screens.Bottom) + } + } + + /** + * @param {Function} callback + */ + onGameLoop(callback) { + callback() + } + + /** + * @param {Function} callback + */ + onControlClicked(callback) { + callback() + } + + /** + * @param {Function} callback + */ + onTouched(callback) { + callback() + } + + invokeKeyboard() { + return null + } + + getSystemLanguage() { + return Languages.DEFAULT + } +} + +class N3DSAsset { + constructor(location) { + this.location = location + } +} + +class Background { + /** + * @param {BackgroundTypes.IMAGE | BackgroundTypes.SOLID | BackgroundTypes} type + * @param {N3DSColorHEX | N3DSColorRGB | N3DSColorRGBA | N3DSAsset} source + */ + constructor(type, source) { + this.type = type + this.source = source + } + + draw(x, y, z, width, height) { + + } +} + +class N3DSColorRGBA { + /** + * @param {Number} red + * @param {Number} green + * @param {Number} blue + * @param {Number} alpha + */ + constructor(red, green, blue, alpha) { + this.red = red + this.green = green + this.blue = blue + this.alpha = alpha + } +} + +class N3DSColorRGB { + /** + * + * @param {Number} red + * @param {Number} green + * @param {Number} blue + */ + constructor(red, green, blue) { + this.red = red + this.green = green + this.blue = blue + this.alpha = 1 + } +} + +class N3DSColorHEX { + /** + * @param {string} hexCode + */ + constructor(hexCode) { + this.hexCode = hexCode + } + + /** + * @returns {N3DSColorRGB} + */ + toRGB() { + const convertedColor = ColorsHelper.fromHexToRGB(this.hexCode) + return convertedColor + } + + toRGBA() { + const convertedColor = ColorsHelper.fromHexToRGBA(this.hexCode) + return convertedColor + } +} + + +class Border { + /** + * @param {Number} top + * @param {Number} left + * @param {Number} right + * @param {Number} bottom + * @param {BorderTypes.DOTTED | BorderTypes.DASHED | BorderTypes.SOLID | BorderTypes.DOUBLE | BorderTypes.RIDGE | BorderTypes.INSET | BorderTypes.OUTSET | BorderTypes.NONE | BorderTypes.MIXED} type + * @param {Background} background + */ + constructor(top, left, right, bottom, type, background, radius) { + this.top = top + this.left = left + this.right = right + this.bottom = bottom + this.type = type || BorderTypes.SOLID + this.background = background || new Background(BackgroundTypes.SOLID, Colors.BLACK) + } +} + +class Font { + /** + * @param {N3DSAsset} source + * @param {string} name + */ + constructor(source, name) { + this.source = source + this.name = name + } +} + +class FontSetting { + /** + * @param {Font} font + * @param {Number} weight + * @param {FontStyles.NORMAL | FontStyles.ITALIC | FontStyles.OBLIQUE} style + */ + constructor(font, weight, style) { + this.font = font + this.weight = weight + this.style = style + } +} + +class N3DSText { + /** + * @param {string} value + * @param {FontSetting} fontSpec + */ + constructor(value, fontSpec) { + this.value = value + this.fontSpec = fontSpec + } +} + +class Placeholder { + /** + * @param {Text} title + * @param {N3DSColorHEX | N3DSColorRGB | N3DSColorRGBA} color + * @param {Number} x + * @param {Number} y + * @param {Number} z + * @param {Background | null} background + * @param {Border} border + */ + constructor(title, color, background, border) { + this.title = title || new N3DSText() + this.color = color || Colors.BLACK + this.background = background || new Background(BackgroundTypes.SOLID, Colors.WHITE) + this.border = border || new Border(10, ) + } + + draw(x, y, z, width, height) { + + } +} + +class InputField { + /** + * @param {Placeholder} placeholder + */ + constructor(placeholder) { + this.placeholder = placeholder || new Placeholder("Enter your text here", Colors.BLACK, 0, null, 0, new Background(BackgroundTypes.SOLID, Colors.WHITE)) + } + + /** + * @param {Function} callback + */ + onChanged(callback) { + callback() + } + + /** + * @param {Function} callback + */ + onKeypressed(callback) { + callback() + } + + /** + * @param {Function} callback + */ + onClicked(callback) { + callback() + } + + /** + * @param {Function} callback + */ + onCoordinatesChanges(callback) { + callback() + } + + /** + * @param {Function} callback + */ + onReset(callback) { + callback() + } + + + draw(x, y, z, width, height) { + this.placeholder.draw(x, y, z, width, height) + } +} + +class Graphics2D { + /** + * @param {N3DSText} text + */ + drawText(text) { + return text + } + + /** + * @param {Number} x + * @param {Number} y + * @param {Number} z + * @param {Number} width + * @param {Number} height + * @param {Background} background + * @param {Border} border + */ + drawRect(x, y, z, width, height, background, border) { + return { x, y, z, width, height, background, border } + } +} + +class N3DSScreen { + /** + * @param {Screens.TOP | Screens.BOTTOM} position + */ + constructor(position) { + this.position = position + this.graphics2d = new Graphics2D() + } +} + +module.exports = { + Background, + BackgroundTypes, + Border, + BorderTypes, + Colors, + Fennec, + Font, + FontSetting, + FontStyles, + Graphics2D, + InputField, + Languages, + N3DSAsset, + N3DSColorHEX, + N3DSColorRGB, + N3DSColorRGBA, + N3DSScreen, + N3DSText, + Placeholder +} \ No newline at end of file diff --git a/build/metadata.smdh b/build/metadata.smdh new file mode 100644 index 0000000000000000000000000000000000000000..4c741b4649453c2e7d745d8c82601678b57c3cb1 GIT binary patch literal 14016 zcmeHNO=uj+6>dkyw4#9f;6opB=t04m0YNlL4lBqZE$_-{XB{H%UL?q&XXP~-IUBE( z4Xni;+*T~;C;+@zu;4%jM+0NLq*@dcl{_jGk zr8wlC|5G$(7pm&{zYCp~;*fj(PtllNsH*4xE_7OoL+<%MMPqiMs-FM5&}k_S`Lp(a zic|SmRTln3z|Yn_Lli{W(mXRTHBo2lt`V517byUrb%9y8JhN&^T4L2+VO!U%aa9+%{6R;eA$Dh6(BFTx@}~j zFC#34g7}VmefeTpQHKgcn^SgJ)}x#Nr|d*w1>+WWW(R3Ef+ zkyC!`Zum(JN1$o6RankN)Tl>|&_Rz5WVgbtfV7XgT+|;8%)~KMBkYg1!baFE#&NTF zf0dSQtn{ztAlq13EO}-RbX(#Gya%2J$d)y!2FM$z>@J{QwZPA_2y(8PuxA7 zca7TQ%;h*9UwZ2g_vZam1FsD){N&WltgSan_VaaqynqOkO+`L5^Zvt=GtJ4L&1u^B zQXJpd`REZV;vCUTVZ`AzR1Z-Ks|)Nm-Q=dG^nC%vmeTQm(U1 zT1rY;C6(ZWUl6B}tZA>8Iug006tqUr-iAC0370Jt7Z@vrt5tEhNAM(U_brcwN_l<eNTRv(|qFSZ}N}N}ctcG{*a1p;Mmw?%1rnA0D&&u;%tcUw&|e+$np?R<+dQ ziKI9Zcj{L%Qoc=AL?viM?Ub|N07L)azWvKMfq!s5wd;GXlaY`11(6402C`1%!h7sl zQgS`G;0j6P{37&k(-uUKA7j&0jzRGI$bsj1?Ml!{W8mE3a--C`eP50-eB zU)Eah3( zAAM8TBXu^qh8|ro`2vt{0^H%nZQa3r(vkO3+)I5@5t9=^4k-kOz`Jizw4H`DWhBje zt_0p(*@Aot)Jtkw^qK?CMNRQO;46aA#I3qkxq~Ec*nC6C6jmbem{{xMRl^!7#e zTF!+z`{2!*CEnXPKFSVU0}XBsH+czCQw=o|_-3h9hVV)uEN#{*xwR&AZ~2Fc9M!-z zzx>{VXTN8=KW+M;`JR!|%wI+tg03NO??C?CYSlcqS~uTZ{__V1GseQX)$Nh*BE5b0 zrCc0;^yrvvX<_BpB3c4-&AL+=9P!KR<2)-p#4=-vu-3w|Oi9ErS~*dp^x+3#nXk&5 zS=adE+*co5%AcHB^ek=e0q>yPg}Dk^J05pE0Pcp&#rr7HE_JH4z@3sB7~3kUpa)#8 zU>UzG+W?_cma(d-y#+cWIf->f;@(q1Y2ObY05hZ2vrhTYY$N2Um(l(?!FL%WcE1+w zyo}cHGg|*;`h$?sj?Z${GR~Z+R_TYHM!;7R-dTtZzO@|ixDK{LzQuA8V+pkaBL?7A z=8PX1PeYp#Uu_}=MYN(V^-m+>YLzc+a;=D0GO&n9^g}ITWq~7CiH}|!w427Pz$@b| zh_N7Q1u!bvIv4R(*pHNp_(Cj#!%>sVH=u(OqhF2}af_MV4phE@nQnzejFy%HP;|Dfj-v?UAZkHE)kRyj3;t&HwWHlgT6Np7CVz$z(=5fIZmK z9=rb-X2f#;Y?K-${c@vp0XL2di2BU)oxeS*lJbcwM+=|7D9;B*s;x6m{|l@|kLw`q zk1^9X^13f#D#%50wQ5>@_|UA4{@~Yg4iO<@6|u2VYOHuhUtY)EW_^NV`nb;)0-66X zc1Akmnz=~mX~SRiSzh#i=lkLp7{7c&W&9Ety^AYnN@M;}kH49Wc}LG5Uap&mm#b#o{MFUBuFhQf#e1(_Uzn(8 zzkPYormyQ+{Gx(BUGa-jN9Q16&%Mkxjz0QqwX}K~XTiPs8#~{7I=pl#e`I}p>Hg}y t`KlS7nm)mJe&MgGN!`4^`nT(UN7yUQyE`mR+)X&DhIoH&yToT({x=o$z_0)S literal 0 HcmV?d00001 diff --git a/lib/transcompiler.js b/lib/transcompiler.js new file mode 100644 index 0000000..2aec5b2 --- /dev/null +++ b/lib/transcompiler.js @@ -0,0 +1,83 @@ +const fs = require("node:fs") +const path = require("node:path") +const acorn = require("acorn") + +const source_file = fs.readFileSync(path.join(process.cwd(), "source", "main.js")) +const parsedCode = acorn.parse(source_file) + +for (const node of parsedCode.body) { + for (const index in node.declarations) { + const declaration = node.declarations[index] + visitNode(declaration) + } +} + +function visitNode(node) { + switch (node.type) { + case "VariableDeclarator": + tranlateVariable(node) + break; + case "CallExpression": + + break; + case "Literal": + + break; + case "Identifier": + + break; + case "ArrowFunctionExpression": + + break; + case "FunctionExpression": + + break; + case "BinaryExpression": + + break; + case "Program": + + break; + case "ExpressionStatement": + + break; + case "BlockStatement": + + break; + + default: + console.error("Unsupported declaration type (too complex): " + node.type) + break + } +} + +function tranlateVariable(node) { + if (node.init.arguments && node.init.arguments[0] && node.init.arguments[0].value.includes("fennec")) { + return + } + if (!node.init) { + return + } + switch (node.init.type) { + case "NewExpression": + translateNewExpression(node) + break + default: + console.error("Unsupported declaration type (too complex): " + node.type) + break + } +} + +function translateNewExpression(nodeInitializer) { + if (nodeInitializer.init.callee) { + switch (nodeInitializer.init.callee.name) { + case "Fennec": + + break; + + default: + console.error("Unsupported declaration type (too complex): " + node.type) + break; + } + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b76457a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,28 @@ +{ + "name": "fennec", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fennec", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e0a4be8 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "fennec", + "version": "0.0.1", + "description": "a simple transpiler project to write 3DS homebrew in js style", + "repository": { + "type": "git", + "url": "https://gitea.azures.fr/ChibiEditor/Fennec" + }, + "license": "MIT", + "author": { + "name": "azures04", + "email": "gilleslazure04@gmail.com", + "url": "https://chibieditor.fr" + }, + "type": "commonjs", + "main": "index.js", + "scripts": { + "cpp:build": "cd runtime && make", + "cpp:clean": "cd runtime && make clean" + }, + "dependencies": { + "acorn": "^8.16.0" + } +} diff --git a/runtime/Makefile b/runtime/Makefile new file mode 100644 index 0000000..0289d67 --- /dev/null +++ b/runtime/Makefile @@ -0,0 +1,39 @@ +DEVKITPRO := C:/Users/Usuario/Documents/softwares/devkitPro +DEVKITARM := $(DEVKITPRO)/devkitARM +LIBCTRU := $(DEVKITPRO)/libctru + +3DSXTOOL := $(DEVKITPRO)/tools/bin/3dsxtool.exe +CXX := $(DEVKITARM)/bin/arm-none-eabi-g++.exe + +include $(DEVKITARM)/3ds_rules + +TARGET := FennecApp +INCLUDES := -Ilib -Igen -I$(LIBCTRU)/include \ + -I$(DEVKITPRO)/libcitro2d/include \ + -I$(DEVKITPRO)/libcitro3d/include + +ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mfpu=vfp +CFLAGS := -g -Wall -O2 -mword-relocations $(ARCH) +CXXFLAGS := $(CFLAGS) -fno-exceptions -fno-rtti $(INCLUDES) + +LDFLAGS := $(ARCH) -specs=3dsx.specs -g +LIBS := -lcitro2d -lcitro3d -lctru -lm + +all: $(TARGET).3dsx + +$(TARGET).3dsx: $(TARGET).elf + "$(3DSXTOOL)" $< $@ \ + --smdh="../build/metadata.smdh" + @echo "🦊 Fennec : Build terminé avec succès !" + +$(TARGET).elf: lib/fennec_core.o gen/out.o + $(CXX) $(LDFLAGS) -o $@ $^ -L$(LIBCTRU)/lib $(LIBS) + +lib/fennec_core.o: lib/fennec_core.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +gen/out.o: gen/out.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +clean: + rm -f lib/*.o gen/*.o *.elf *.3dsx \ No newline at end of file diff --git a/runtime/gen/out.cpp b/runtime/gen/out.cpp new file mode 100644 index 0000000..dde9285 --- /dev/null +++ b/runtime/gen/out.cpp @@ -0,0 +1,29 @@ +#include "fennec_core.h" + +int main() { + Fennec::init(); + + u32 white = Fennec::color(255, 255, 255); + u32 red = Fennec::color(255, 0, 0); + u32 blue = Fennec::color(0, 0, 255); + + while (aptMainLoop()) { + hidScanInput(); + if (hidKeysDown() & KEY_START) break; + + Fennec::beginFrame(); + + Fennec::selectScreen(GFX_TOP); + Fennec::drawRect(10, 10, 380, 220, blue); // Un fond bleu + Fennec::drawText(50, 100, "BIENVENUE SUR FENNEC", white); + + Fennec::selectScreen(GFX_BOTTOM); + Fennec::drawRect(50, 50, 100, 100, red); + Fennec::drawText(60, 160, "Carré rouge :)", white); + + Fennec::endFrame(); + } + + Fennec::exit(); + return 0; +} \ No newline at end of file diff --git a/runtime/lib/fennec_core.cpp b/runtime/lib/fennec_core.cpp new file mode 100644 index 0000000..51a0b41 --- /dev/null +++ b/runtime/lib/fennec_core.cpp @@ -0,0 +1,64 @@ +#include "fennec_core.h" + +namespace Fennec { + static C3D_RenderTarget* topTarget; + static C3D_RenderTarget* bottomTarget; + static C3D_RenderTarget* currentTarget; + + static C2D_TextBuf staticTextBuf; + + void init() { + gfxInitDefault(); + C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); + C2D_Init(C2D_DEFAULT_MAX_OBJECTS); + C2D_Prepare(); + + staticTextBuf = C2D_TextBufNew(4096); + + topTarget = C2D_CreateScreenTarget(GFX_TOP, GFX_LEFT); + bottomTarget = C2D_CreateScreenTarget(GFX_BOTTOM, GFX_LEFT); + + currentTarget = topTarget; + } + + void drawRect(float x, float y, float z, float w, float h, u32 color) { + C2D_DrawRectSolid(x, y, 0.5f, w, h, color); + } + + void drawText(float x, float y, std::string content, u32 color) { + if (!staticTextBuf) return; + C2D_Text textObj; + C2D_TextParse(&textObj, staticTextBuf, content.c_str()); + C2D_TextOptimize(&textObj); + C2D_DrawText(&textObj, C2D_WithColor, x, y, 0.5f, 0.1f, 0.1f, color); + } + + void clearTextBuffer() { + C2D_TextBufClear(staticTextBuf) + } + + void exit() { + C2D_TextBufDelete(staticTextBuf); + C2D_Fini(); + C3D_Fini(); + gfxExit(); + } + + void beginFrame() { + C3D_FrameBegin(C3D_FRAME_SYNCDRAW); + C2D_TextBufClear(staticTextBuf); + } + + void endFrame() { + C3D_FrameEnd(0); + } + + void selectScreen(gfxScreen_t screen) { + currentTarget = (screen == GFX_TOP) ? topTarget : bottomTarget; + C2D_SceneBegin(currentTarget); + } + + u32 color(u8 r, u8 g, u8 b, u8 a) { + return C2D_Color32(r, g, b, a); + } +} \ No newline at end of file diff --git a/runtime/lib/fennec_core.h b/runtime/lib/fennec_core.h new file mode 100644 index 0000000..28eca45 --- /dev/null +++ b/runtime/lib/fennec_core.h @@ -0,0 +1,21 @@ +#ifndef FENNEC_CORE_H +#define FENNEC_CORE_H + +#include <3ds.h> +#include +#include + +namespace Fennec { + void init(); + void exit(); + void beginFrame(); + void endFrame(); + + void drawRect(float x, float y, float w, float h, u32 color); + void drawText(float x, float y, std::string content, u32 color); + + void selectScreen(gfxScreen_t screen); + u32 color(u8 r, u8 g, u8 b, u8 a = 255); +} + +#endif \ No newline at end of file diff --git a/source/main.js b/source/main.js new file mode 100644 index 0000000..ad5ac57 --- /dev/null +++ b/source/main.js @@ -0,0 +1,11 @@ +const { Fennec, N3DSText } = require("../bin/fennec") + +const game = new Fennec() +const topScreenGraph = game.screens.Top.graphics2d +const bottomScreenGraph = game.screens.Bottom.graphics2d +const loremIpsum = new N3DSText("Hello world") + +game.onGameLoop(() => { + topScreenGraph.drawRect(0, 0, 0.5, 100, 20, null, null) + bottomScreenGraph.drawText(loremIpsum) +}) \ No newline at end of file