First commit

This commit is contained in:
Gilles Lazures 2026-03-15 18:36:05 +01:00
commit 9dfc56eb4a
12 changed files with 624 additions and 0 deletions

3
.css Normal file
View File

@ -0,0 +1,3 @@
body {
font-style: ;
}

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/node_modules

321
bin/fennec.js Normal file
View File

@ -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
}

BIN
build/metadata.smdh Normal file

Binary file not shown.

83
lib/transcompiler.js Normal file
View File

@ -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;
}
}
}

28
package-lock.json generated Normal file
View File

@ -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"
}
}
}
}

24
package.json Normal file
View File

@ -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"
}
}

39
runtime/Makefile Normal file
View File

@ -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

29
runtime/gen/out.cpp Normal file
View File

@ -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;
}

View File

@ -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);
}
}

21
runtime/lib/fennec_core.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef FENNEC_CORE_H
#define FENNEC_CORE_H
#include <3ds.h>
#include <citro2d.h>
#include <string>
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

11
source/main.js Normal file
View File

@ -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)
})