1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
use image::{self, GenericImageView, ImageReader};
#[allow(unused_imports)]
use regex::Regex;
use std::{fs, process::Command};
use cc;
mod utils {
include!("utils.rs");
}
const ASSETS_DIR: &str = "assets/";
const POSSIBLE_C_DIRS: [&str; 4] = ["src/libs", "src/lib", "src/c", "src/cpp"];
#[allow(dead_code)]
const KEYBOARD_MAPPING_FILE: &str = "epsilon_simulator/ion/src/simulator/shared/keyboard.cpp";
const ICON_WIDTH: u32 = 55;
const ICON_HEIGHT: u32 = 56;
macro_rules! cargo_warn {
($fmt:expr, $($arg:tt)*) => {
println!("cargo:warning={}", format!($fmt, $($arg)*))
};
($fmt:expr) => {
println!("cargo:warning={}", $fmt)
};
}
macro_rules! cargo_changed {
($path:expr) => {
println!("cargo:rerun-if-changed={}", $path);
};
}
fn convert_image(file_path: &std::path::Path) {
let img = ImageReader::open(file_path)
.unwrap()
.decode()
.unwrap();
let mut converted_pixels: Vec<u8> = Vec::new();
// Ajouter l'en-tête EIF1 au DÉBUT
converted_pixels.extend(utils::EIF1_MAGIC_NUMBER.to_le_bytes()); // Magic number (4 bytes)
converted_pixels.extend((img.width() as u16).to_le_bytes()); // Width (2 bytes)
converted_pixels.extend((img.height() as u16).to_le_bytes()); // Height (2 bytes)
// Ensuite ajouter les pixels RGB565
for pix in img.pixels() { // Convert to RGB565
// TODO: Utiliser une méthode configuration pour indiquer la couleur de transparence
// Si le pixel est complètement transparent (ex: background PNG)
if pix.2.0[3] == 0 {
// Pixel transparent, le convertir en blanc
converted_pixels.extend(0xFFFFu16.to_le_bytes()); // Blanc en RGB565
continue;
}
let rgb565 = ((pix.2.0[0] as u16 & 0b11111000) << 8) // Mettre les bits rouges a 15-11
| ((pix.2.0[1] as u16 & 0b11111100) << 3) // Mettre les bits verts a 10-5
| (pix.2.0[2] as u16 >> 3); // Mettre les bits bleus a 4-0
converted_pixels.extend(rgb565.to_le_bytes()); // 2 octets
}
// Nom du fichier sans le chemin
let out_name = file_path.file_name().unwrap().to_str().unwrap();
// Définir le dossier de sortie des assets convertis
let out_dir = format!("{}/assets", std::env::var("OUT_DIR").unwrap());
// Créer le dossier OUT_DIR/assets/ s'il n'existe pas
fs::create_dir_all(&out_dir)
.expect("Failed to create output assets/ directory");
// Écrire le fichier converti au format .bin
fs::write(format!("{}/{}.eif", out_dir, out_name), converted_pixels.as_slice())
.expect("Failed to write converted image file");
}
pub fn setup() {
setup_with_options(None, None);
}
/// Configure et exécute le processus de build complet avec des options personnalisées
pub fn setup_with_options(
asset_dir: Option<&str>,
c_dirs: Option<Vec<&str>>,
) {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
let asset_dir = std::path::Path::new(&manifest_dir).join(asset_dir.unwrap_or(ASSETS_DIR));
let c_dirs: Vec<String> = c_dirs
.unwrap_or(POSSIBLE_C_DIRS.iter().map(|s| *s).collect())
.into_iter()
.map(|d| std::path::Path::new(&manifest_dir).join(d).to_string_lossy().to_string())
.collect();
// Créer le dossier assets/ s'il n'existe pas
fs::create_dir_all(&asset_dir)
.expect("Failed to create assets/ directory");
let entries = fs::read_dir(&asset_dir)
.expect("Failed to read assets/ directory");
for entry_result in entries {
// Stoper le build si une erreur survient lors de la lecture d'un fichier
let entry = entry_result.unwrap_or_else(|err| {
panic!("Failed to read entry in assets/: {}", err);
});
let path = entry.path();
// Ignorer le fichier s'il n'a pas d'extension
// Definir 'ext'
let ext = match path.extension() {
Some(e) => e,
None => {
cargo_warn!("Ignoring file without extension: {}", path.display());
continue;
},
};
// ignorer le fichier s'il n'a pas de nom
// Definir 'name'
let name = match path.file_stem() {
Some(e) => e,
None => {
cargo_warn!("No file stem (name) for file: {}", path.display());
continue;
}
};
// Ignorer le fichier s'il n'est pas un .png
if ext != "png" {
cargo_warn!("Ignoring non-png file: {}", path.display());
continue;
};
// Si le fichier est l'icône, le convertir en .nwi
// Sinon, le convertir en .bin
if name == "icon" {
// Vérifier que l'image est de la bonne taille (55×56 pixels)
// Charger l'image
let img = ImageReader::open(&path);
// Stoper le build si une erreur survient lors de l'ouverture de l'image
let img = img.unwrap_or_else(|err| {
panic!("Failed to open icon image {}: {}", path.display(), err);
});
// Stoper le build si une erreur survient lors du décodage de l'image
let img = img.decode().unwrap_or_else(|err| {
panic!("Failed to decode icon image {}: {}", path.display(), err);
});
let (width, height) = img.dimensions();
assert_eq!(width, ICON_WIDTH, "Icon width must be {} pixels", ICON_WIDTH); // Stoper le build si la largeur est incorrecte
assert_eq!(height, ICON_HEIGHT, "Icon height must be {} pixels", ICON_HEIGHT); // Stoper le build si la hauteur est incorrecte
// Utiliser nwlink pour convertir l'image en .nwi
// Définir le chemin de sortie dans OUT_DIR
let out_dir = std::env::var("OUT_DIR").unwrap();
let icon_out_path = format!("{}/icon.nwi", out_dir);
let output = Command::new("sh")
.arg("-c")
.arg(format!("npx --yes -- nwlink@0.0.19 png-nwi {} {}", path.display(), icon_out_path))
.output()
.expect("Failed to run nwlink for icon.nwi");
// Stoper le build si nwlink retourne une erreur
assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
} else {
// Convertir l'image en binaire RGB565
convert_image(&path);
}
};
// Compilation et linkage des fichiers C/C++ présent dans src/libs/, src/lib/, src/c ou src/cpp
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let is_embedded = target_os == "none";
// Configuration du compilateur C pour ARM (architecture de la NumWorks) uniquement pour l'embarqué
if is_embedded {
unsafe { std::env::set_var("CC", "arm-none-eabi-gcc") };
}
// Récupération des flags de compilation spécifiques à EADK via nwlink (uniquement pour l'embarqué)
let nwlink_flags = if is_embedded {
let program = "npx";
String::from_utf8(
Command::new(program)
.args(["--yes", "--", "nwlink@0.0.19", "eadk-cflags"])
.output()
.expect("Failed to get nwlink eadk-cflags")
.stdout,
)
.expect("Invalid UTF-8 in nwlink flags")
} else {
String::new()
};
let mut build = cc::Build::new();
let mut has_files = false;
// Fonction récursive pour ajouter les fichiers C/C++ d'un répertoire
fn add_c_files_recursive(dir: &std::path::Path, build: &mut cc::Build) -> Result<bool, std::io::Error> {
let mut found = false;
// Obtenir la liste du contenu du répertoire
let entries = match fs::read_dir(dir) {
Ok(entries) => entries,
Err(e) => return Err(e),
};
// Parcourir chaque entrée dans le répertoire
for entry in entries.flatten() {
let path = entry.path();
// Si l'entrée est un répertoire, descendre récursivement
if path.is_dir() {
// Descendre récursivement dans les sous-répertoires
match add_c_files_recursive(&path, build) {
Ok(sub_found) => found |= sub_found, // True si 'found' est déja true alors que 'sub_found' est false. Sinon, prend la valeur de 'sub_found'.
Err(e) => return Err(e), // Propager l'erreur vers l'appelant
};
}
// Sinon, vérifier l'extension du fichier
else if let Some(ext) = path.extension() {
// Vérifier si le fichier est un .c ou .cpp
if ext == "c" || ext == "cpp" {
// Indiquer à Cargo de relancer le build si ce fichier change
cargo_changed!(path.display());
// Ajouter le fichier à la liste de compilation
build.file(path);
found = true;
};
};
};
// You may want to implement the recursive logic here, or leave as is.
Ok(found)
}
// Parcourir chaque répertoire racine pour trouver les fichiers C/C++
for dir_path in c_dirs {
let libs_dir = std::path::Path::new(&dir_path);
if libs_dir.exists() {
// Ajouter les fichiers trouvés dans ce répertoire
has_files |= match add_c_files_recursive(libs_dir, &mut build) {
Ok(found) => found, // True si des fichiers ont été trouvés
Err(e) => panic!("Failed to read C/C++ files from {}: {}", dir_path, e), // Panique en cas d'erreur
};
};
};
// Configuration des flags de compilation C
build.flag("-std=c99"); // Standard C99
build.flag("-Wall"); // Tous les avertissements
build.flag("-ggdb"); // Informations de debug pour GDB
build.warnings(false); // Ne pas traiter les warnings comme des erreurs
// Flags spécifiques à l'embarqué (optimisation taille et sections séparées)
if is_embedded {
build.flag("-Os"); // Optimisation pour la taille (important pour l'embarqué)
build.flag("-ffunction-sections"); // Chaque fonction dans sa propre section
build.flag("-fdata-sections"); // Chaque variable dans sa propre section
// Ajouter les flags spécifiques à EADK (chemins d'include, macros, etc.)
for flag in nwlink_flags.split_whitespace() {
build.flag(flag);
}
}
// Si aucun fichier C/C++ n'a été trouvé, créer un fichier vide
// pour éviter les erreurs du linker (qui attend une bibliothèque)
if !has_files {
let out_dir = std::env::var("OUT_DIR").unwrap();
let empty_c = format!("{}/empty.c", out_dir);
fs::write(&empty_c, "// Empty C file to satisfy linker when no C/C++ files are present\n").unwrap();
build.file(&empty_c);
}
// Compiler et créer la bibliothèque statique libnative_libs.a
// Ce nom doit correspondre au flag -lnative_libs dans .cargo/config.toml
build.compile("native_libs");
// // Remapper les touches du simulateur NumWorks en modifiant keyboard.cpp
// if std::env::var("CARGO_CFG_TARGET_OS").unwrap() != "none" {
// if let Some(keyboard_file) = Some(&keyboard_mapping_file) {
// cargo_changed!(keyboard_file);
// // Nouveau mapping de touches personnalisé pour le simulateur
// let remapped = "constexpr static KeySDLKeyPair sKeyPairs[] = {\
// KeySDLKeyPair(Key::OK, SDL_SCANCODE_RETURN),\
// KeySDLKeyPair(Key::Back, SDL_SCANCODE_BACKSPACE),\
// KeySDLKeyPair(Key::EXE, SDL_SCANCODE_ESCAPE),\
// \
// KeySDLKeyPair(Key::Var, SDL_SCANCODE_I),\
// \
// KeySDLKeyPair(Key::Toolbox, SDL_SCANCODE_W),\
// KeySDLKeyPair(Key::Imaginary, SDL_SCANCODE_A),\
// KeySDLKeyPair(Key::Power, SDL_SCANCODE_D),\
// KeySDLKeyPair(Key::Comma, SDL_SCANCODE_S),\
// KeySDLKeyPair(Key::Shift, SDL_SCANCODE_SPACE),\
// KeySDLKeyPair(Key::Exp, SDL_SCANCODE_LSHIFT),\
// \
// KeySDLKeyPair(Key::Down, SDL_SCANCODE_DOWN),\
// KeySDLKeyPair(Key::Up, SDL_SCANCODE_UP),\
// KeySDLKeyPair(Key::Left, SDL_SCANCODE_LEFT),\
// KeySDLKeyPair(Key::Right, SDL_SCANCODE_RIGHT),\
// };";
// // Lire le fichier de configuration dukeyboard_filer
// let file_content = fs::read_to_string(keyboard_file)
// .expect("Cannot open keyboard.cpp file from emulator. Please check if the simulator is clonned properly.");
// // Vérifier si le mapping n'est pas déjà appliqué pour éviter les réécritures inutiles
// if !file_content.contains(remapped) {
// // Utiliser une regex pour trouver et remplacer l'ancien tableau de mapping
// // Pattern: "constexpr static KeySDLKeyPair sKeyPairs[] = { ... };"
// let re = Regex::new(r"constexpr static KeySDLKeyPair sKeyPairs\[] ?= ?\{[\S\s]*?};")
// .unwrap();
// let _result = re.replace(&file_content, remapped);
// // Écrire le nouveau contenu avec le mapping personnalisé
// // fs::write(
// // KEYBOARD_MAPPING_FILE,
// // result.as_bytes(),
// // )
// // .unwrap();
// // Désactivation du mapping custom de touches.
// }
// }
// }
}