#include #include #include #include #include #include #include #include #include #include #include #include #include "tomlc99/toml.h" // FIXME: This is hardcoded, fix it. char *python_interpreter_path = "/usr/local/bin/python3"; bool can_run_command(const char *cmd) { if(strchr(cmd, '/')) { return access(cmd, X_OK)==0; } const char *path = getenv("PATH"); if(!path) return false; char *buf = malloc(strlen(path)+strlen(cmd)+3); if(!buf) return false; for(; *path; ++path) { char *p = buf; for(; *path && *path!=':'; ++path,++p) *p = *path; if(p==buf) *p++='.'; if(p[-1]!='/') *p++='/'; strcpy(p, cmd); if(access(buf, X_OK)==0) { free(buf); return true; } if(!*path) break; } free(buf); return false; } #define BUFFER_SIZE 1024 bool copy_directory(const char *src_path, const char *dest_path) { DIR *src_dir = opendir(src_path); if (src_dir == NULL) { // Failed to open source directory return false; } // Create destination directory if it doesn't exist mkdir(dest_path, 0755); // Copy files and directories from source to destination struct dirent *entry; while ((entry = readdir(src_dir)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { // Skip "." and ".." directories continue; } char src_file_path[BUFFER_SIZE]; snprintf(src_file_path, BUFFER_SIZE, "%s/%s", src_path, entry->d_name); char dest_file_path[BUFFER_SIZE]; snprintf(dest_file_path, BUFFER_SIZE, "%s/%s", dest_path, entry->d_name); struct stat src_file_stat; stat(src_file_path, &src_file_stat); if (S_ISDIR(src_file_stat.st_mode)) { // Recursively copy directories if (!copy_directory(src_file_path, dest_file_path)) { return false; } } else if (S_ISREG(src_file_stat.st_mode)) { // Copy regular files FILE *src_file = fopen(src_file_path, "rb"); if (src_file == NULL) { // Failed to open source file return false; } FILE *dest_file = fopen(dest_file_path, "wb"); if (dest_file == NULL) { // Failed to open destination file fclose(src_file); return false; } // Copy file contents char buffer[BUFFER_SIZE]; size_t bytes_read; while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, src_file)) > 0) { fwrite(buffer, 1, bytes_read, dest_file); } fclose(src_file); fclose(dest_file); } else { // Skip other file types continue; } } closedir(src_dir); return true; } void clear_directory(char const *path) { DIR *dir = opendir(path); if (dir == NULL) { // Unable to open the directory perror("opendir"); return; } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { // Skip "." and ".." entries if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } // Get the full path of the entry char entry_path[1024]; snprintf(entry_path, sizeof(entry_path), "%s/%s", path, entry->d_name); // Check if the entry is a directory struct stat entry_stat; if (stat(entry_path, &entry_stat) != 0) { perror("stat"); continue; } if (S_ISDIR(entry_stat.st_mode)) { // Remove the directory recursively clear_directory(entry_path); if (rmdir(entry_path) != 0) { perror("rmdir"); } } else { // Remove the entry if (unlink(entry_path) != 0) { perror("unlink"); } } } closedir(dir); } #define MANIFEST_FNAME "tos_project.toml" char* find_project_root(void) { char* result = NULL; // Get the current working directory char cwd[1024]; if (getcwd(cwd, sizeof(cwd)) == NULL) { return NULL; } // Check if the current working directory contains the file "tos_project.toml" size_t file_path_len = strlen(cwd) + strlen("/tos_project.toml") + 1; char file_path[file_path_len]; snprintf(file_path, file_path_len, "%s/tos_project.toml", cwd); if (access(file_path, F_OK) == 0) { // The file exists, so we can return the path to the directory containing it result = malloc(strlen(cwd) + 1); strncpy(result, cwd, strlen(cwd) + 1); return result; } // The file does not exist in the current working directory, so we need to search backwards size_t len = strlen(cwd); for (int i = len - 1; i >= 0; i--) { if (cwd[i] == '/') { // We found a directory separator, so we can check if the parent directory contains the file cwd[i] = '\0'; snprintf(file_path, file_path_len, "%s/tos_project.toml", cwd); if (access(file_path, F_OK) == 0) { // The file exists, so we can return the path to the parent directory containing it result = malloc(strlen(cwd) + 1); strncpy(result, cwd, strlen(cwd) + 1); break; } } } return result; } #define SAMPLE_MANIFEST "[General]\n" \ "Name=\"%s\"\n" \ "Author=\"%s\"\n" \ "Version=\"0.1\"\n" \ "\n" \ "[Dependencies]\n" typedef struct project_manifest { char *name, *author, *version; // FIXME: Add dependencies. } project_manifest; void free_manifest(project_manifest *manifest) { free(manifest->name); free(manifest->author); free(manifest->version); } project_manifest* load_manifest(char const *path) { FILE* fp; char errbuf[200]; fp = fopen(path, "r"); if (!fp) { fputs("Error: Cannot load manifest file: Cannot open file.\n", stderr); return NULL; } project_manifest *new = calloc(1, sizeof(project_manifest)); toml_table_t *conf = toml_parse_file(fp, errbuf, sizeof(errbuf)); fclose(fp); if (!conf) { fprintf(stderr, "Error: Cannot load manifest file: Cannot parse file.\n", errbuf); return NULL; } toml_table_t *general = toml_table_in(conf, "General"); if (!general) { fprintf(stderr, "Error: Cannot load manifest file: Cannot find [General] table.\n", errbuf); return NULL; } toml_datum_t name = toml_string_in(general, "Name"); if (!name.ok) { fprintf(stderr, "Error: Cannot load manifest file: Cannot find Name field.\n", errbuf); return NULL; } toml_datum_t author = toml_string_in(general, "Author"); if (!author.ok) { fprintf(stderr, "Error: Cannot load manifest file: Cannot find Author field.\n", errbuf); return NULL; } toml_datum_t version = toml_string_in(general, "Version"); if (!version.ok) { fprintf(stderr, "Error: Cannot load manifest file: Cannot find Version field.\n", errbuf); return NULL; } new->name = name.u.s; new->author = author.u.s; new->version = version.u.s; // FIXME: Add dependencies. toml_free(conf); printf("%s, %s, %s\n", new->name, new->author, new->version); return new; } char buffer_text_format[2048]; char *text_format(char const *format, ...) { va_list args; va_start(args, format); vsprintf(buffer_text_format, format, args); va_end(args); return buffer_text_format; } char *get_username(void) { #if defined(_WIN32) // FIXME: This leaks memory. TCHAR infoBuf[105]; DWORD bufCharCount = 105; if( !GetUserName( infoBuf, &bufCharCount ) ) printError( TEXT("GetUserName") ); char *c_szText[105]; wcstombs(c_szText, infoBuf, wcslen(infoBuf) + 1); return c_szText; #else uid_t uid = geteuid(); struct passwd *pw = getpwuid(uid); if (pw == NULL) return NULL; return pw->pw_name; #endif } bool create_manifest_file(char const *project_path) { char *name; char cwd[4096]; if (strlen(project_path) == 1 && project_path[0] == '.') { if (getcwd(cwd, sizeof(cwd)) == NULL) { fputs("Error: Cannot create manifest: Cannot get current working directory.\n", stderr); return false; } else { name = malloc((strlen(cwd)+1)*sizeof(char)); strcpy(name, cwd); } } else { name = basename((char *)project_path); } char *uname = get_username(); FILE *fd = fopen(text_format("%s/" MANIFEST_FNAME, project_path), "w+"); if (fd == NULL) { fputs("Error: Cannot create manifest: Cannot open manifest file.\n", stderr); return false; } fprintf(fd, SAMPLE_MANIFEST, name, uname); fclose(fd); return true; } void print_help(char **argv) { printf("Usage: %s [command]\n", argv[0]); fputs("\nCommands:\n", stderr); fputs(" * init|i [path=.] - Setup a new project.\n", stderr); fputs(" * build|. - Build project in current working directory.\n", stderr); fputs(" * clean - Clean output code in current working directory.\n", stderr); fputs("\nTo see manifest file usage, check out man tbuild(1)\n", stderr); } bool file_exists(char const *path) { struct stat st = {0}; return (stat(path, &st) != -1); } // FIXME: Implement for Windows. bool makedir(char const *path) { struct stat st = {0}; if (stat(path, &st) == -1) { mkdir(path, 0700); } else { fprintf(stderr, "Warning: File already exists. Continuing anyway."); return false; } return true; } bool makedir_parenting(char const *path) { char *p; struct stat st = {0}; for(p=strchr(path+1, '/'); p; p=strchr(p+1, '/')){ *p = 0; if(stat(path, &st) == -1 && makedir(path) == true) return true; *p = '/'; } if(stat(path, &st) == -1) makedir(path); return true; } void replace_in_file(char const *file, char const *text_to_find, char const *text_to_replace) { FILE *input = fopen(file, "r"); FILE *output = fopen("temp.txt", "w"); char buffer[512]; while (fgets(buffer, sizeof(buffer), input) != NULL) { char *pos = strstr(buffer, text_to_find); if (pos == NULL) { fputs(buffer, output); continue; } char *temp = calloc(strlen(buffer) - strlen(text_to_find) + strlen(text_to_replace) + 1, 1); memcpy(temp, buffer, pos - buffer); memcpy(temp + (pos - buffer), text_to_replace, strlen(text_to_replace)); memcpy(temp + (pos - buffer) + strlen(text_to_replace), pos + strlen(text_to_find), 1 + strlen(buffer) - ((pos - buffer) + strlen(text_to_find))); fputs(temp, output); free(temp); } fclose(output); fclose(input); rename("temp.txt", file); } void convert_to_zealos(char const *path) { DIR *dir; struct dirent *entry; struct stat s; if (!(dir = opendir(path))) return; if (!(entry = readdir(dir))) return; do { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; char fpath[1024]; int len = snprintf(fpath, sizeof(fpath)-1, "%s/%s", path, entry->d_name); fpath[len] = 0; if (lstat(fpath, &s) == 0 && S_ISDIR(s.st_mode)) { // Is directory? convert_to_zealos(fpath); continue; } if (fpath[len-2] != 'H' || fpath[len-3] != '.') continue; printf("Converting %s\n", entry->d_name); replace_in_file(fpath, "MemCpy", "MemCopy"); replace_in_file(fpath, "MemCpy", "MemCopy"); replace_in_file(fpath, "MemCmp", "MemCompare"); replace_in_file(fpath, "StrCpy", "StrCopy"); replace_in_file(fpath, "StrCmp", "StrCompare"); replace_in_file(fpath, "StrICmp", "StrICompare"); replace_in_file(fpath, "StrNCmp", "StrNCompare"); replace_in_file(fpath, "StrNICmp", "StrNICompare"); replace_in_file(fpath, "BEqu", "BEqual"); replace_in_file(fpath, "LBEqu", "LBEqual"); replace_in_file(fpath, "ms", "mouse"); replace_in_file(fpath, "Snd", "Sound"); replace_in_file(fpath, "SndTaskEndCB", "SoundTaskEndCB"); replace_in_file(fpath, "mp_cnt", "mp_count"); replace_in_file(fpath, "QueIns", "QueueInsert"); replace_in_file(fpath, "QueInit", "QueueInit"); replace_in_file(fpath, "QueRem", "QueueRemove"); replace_in_file(fpath, "QueDel", "QueueDel"); replace_in_file(fpath, "MsSet", "MouseSet"); replace_in_file(fpath, "UnusedStk", "UnusedStack"); replace_in_file(fpath, "word_lst", "word_list"); replace_in_file(fpath, "FileExtRem", "FileExtRemove"); replace_in_file(fpath, "cnts", "counts"); replace_in_file(fpath, "PostMsg", "MessagePost"); replace_in_file(fpath, "PostMsgWait", "MessagePostWait"); replace_in_file(fpath, "QSort", "QuickSort"); replace_in_file(fpath, "QSortI64", "QuickSortI64"); replace_in_file(fpath, "ScanMsg", "MessageScan"); replace_in_file(fpath, "cnts", "counts"); replace_in_file(fpath, "Dsk", "Disk"); replace_in_file(fpath, "collision_cnt", "collision_count"); replace_in_file(fpath, "Drv", "Drive"); replace_in_file(fpath, "DrvRep", "DriveRep"); replace_in_file(fpath, "Drv2Let", "Drive2Letter"); replace_in_file(fpath, "LstSub", "ListSub"); replace_in_file(fpath, "LstMatch", "ListMatch"); replace_in_file(fpath, "DefineLstLoad", "DefineListLoad"); replace_in_file(fpath, "ExtDft", "ExtDefault"); replace_in_file(fpath, "ExtChg", "ExtChange"); replace_in_file(fpath, "RegDft", "RegDefault"); replace_in_file(fpath, "\"HC\"", "\"CC\""); replace_in_file(fpath, "CDrv", "CDrive"); replace_in_file(fpath, "CDbgInfo", "CDebugInfo"); replace_in_file(fpath, "dbg_info", "debug_info"); replace_in_file(fpath, "StrFirstRem", "StrFirstRemove"); replace_in_file(fpath, "StrLastRem", "StrLastRemove"); replace_in_file(fpath, "TempleOS/Apps", "/Apps"); replace_in_file(fpath, "adam_task", "sys_task"); replace_in_file(fpath, "JobQue", "JobQueue"); replace_in_file(fpath, "MSG_", "MESSAGE_"); replace_in_file(fpath, ".HC", ".ZC"); replace_in_file(fpath, "Msg", "Message"); replace_in_file(fpath, "MusicSettingsRst", "MusicSettingsReset"); replace_in_file(fpath, "hndlr", "handler"); replace_in_file(fpath, "FifoU8Rem", "FifoU8Remove"); replace_in_file(fpath, "GodBitsIns", "GodBitsInsert"); replace_in_file(fpath, "fp_draw_ms", "fp_draw_mouse"); replace_in_file(fpath, "DrawStdMs", "DrawStdMouse"); replace_in_file(fpath, "WIG_TASK_DFT", "WIG_TASK_DEFAULT"); replace_in_file(fpath, "DirMk", "DirMake"); replace_in_file(fpath, "GetI64", "I64Get"); replace_in_file(fpath, "GetF64", "F64Get"); replace_in_file(fpath, "GetStr", "StrGet"); replace_in_file(fpath, "GetChar", "CharGet"); // Added from Anfintony's Insecticide November 24 2022 replace_in_file(fpath, "GetMsg", "MessageGet"); replace_in_file(fpath, "DRV_SIGNATURE_VAL", "DRIVE_SIGNATURE_VAL"); replace_in_file(fpath, "dv_signature", "drive_signature"); replace_in_file(fpath, "DrvTextAttrGet", "DriveTextAttrGet"); replace_in_file(fpath, "DrvIsWritable", "DriveIsWritable"); replace_in_file(fpath, "gr_palette_std", "gr32_palette_std"); replace_in_file(fpath, "GetKey", "KeyGet"); replace_in_file(fpath, "STD_DISTRO_DVD_CFG", "STD_DISTRO_DVD_CONFIG"); replace_in_file(fpath, "CBGR48", "CBGR24"); replace_in_file(fpath, "CFreeLst", "CFreeList"); replace_in_file(fpath, "DrvLock", "DriveLock"); replace_in_file(fpath, "DrvUnlock", "DriveUnlock"); replace_in_file(fpath, "DrvChk", "DriveCheck"); replace_in_file(fpath, "AMAlloc", "SysMAlloc"); replace_in_file(fpath, "Let2Drv", "Letter2Drive"); replace_in_file(fpath, "Let2Let", "Letter2Letter"); replace_in_file(fpath, "Let2BlkDev", "Letter2BlkDev"); replace_in_file(fpath, "first_drv_let", "first_drive_let"); // Added by Doodguy and Anfintony November 25 2022 replace_in_file(fpath, "ScanKey", "KeyScan"); replace_in_file(fpath, "ScanChar", "CharScan"); replace_in_file(fpath, "fp_final_scrn_update", "fp_final_screen_update"); entry->d_name[strlen(entry->d_name)-2] = 'Z'; rename(fpath, text_format("%s/%s", path, entry->d_name)); } while ((entry = readdir(dir))); closedir(dir); } bool run_scripts(char const *path) { DIR *dir; struct dirent *entry; struct stat s; if (!(dir = opendir(path))) return false; if (!(entry = readdir(dir))) return false; do { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; char fpath[1024]; int len = snprintf(fpath, sizeof(fpath)-1, "%s/%s", path, entry->d_name); fpath[len] = 0; if (lstat(fpath, &s) == 0 && S_ISDIR(s.st_mode)) { // Is directory? run_scripts(fpath); continue; } if (fpath[len-3] != 'p' || fpath[len-2] != 'y' || fpath[len-3] != '.') continue; printf("Running %s\n", entry->d_name); char *argv[] = { fpath, NULL }; execve(python_interpreter_path, argv, NULL); } while ((entry = readdir(dir))); closedir(dir); return true; } int main(int argc, char **argv) { if (argc < 2) { puts("No arguments provided.\n"); print_help(argv); return 0; } char path_save[PATH_MAX]; char abs_exe_path[PATH_MAX]; char *p; if(!(p = strrchr(argv[0], '/'))) getcwd(abs_exe_path, sizeof(abs_exe_path)); else { *p = '\0'; getcwd(path_save, sizeof(path_save)); chdir(argv[0]); getcwd(abs_exe_path, sizeof(abs_exe_path)); chdir(path_save); } char *project_path = "."; char cmd = tolower(argv[1][0]); if (cmd == 'i') { if (argc > 2) { project_path = calloc(1, (strlen(argv[2])+1)*sizeof(char)); strcpy(project_path, argv[2]); } fprintf(stderr, "Initializing project in `%s`.\n", project_path); bool ret = true; if (!(strlen(project_path) == 1 && project_path[0] == '.')) ret = makedir_parenting(project_path); if (!ret) { fputs("Error: Cannot create project: Cannot create directories.\n", stderr); return -1; } // Check if manifest file exists. if (file_exists(text_format("%s/" MANIFEST_FNAME, project_path))) { fputs("Error: Cannot create project: Project already exists!\n", stderr); return -1; } create_manifest_file(project_path); if (can_run_command("git")) system(text_format("git init %s", project_path)); // Free only if alloc'ed, "." doesn't count since it is embedded in the program itself. // Trying to free it if it's "." would cause a crash cause of this. if (argc > 2) free(project_path); } else if (cmd == 'b' || cmd == '.') { struct { bool zeal_build; } options = { 0 }; for (int i = 2; i < argc; i++) { if (argv[i][0] != '-') continue; if (argv[i][1] == '-') { if (strcmp(argv[i], "--zeal")) options.zeal_build = true; continue; } for (int j = 1; j < strlen(argv[i]); j++) { switch (argv[i][j]) { case 'z': case 'Z': options.zeal_build = true; break; } } } // Find project root. char cwd[4096]; if (getcwd(cwd, sizeof(cwd)) == NULL) { fputs("Error: Cannot build project: Cannot get current working directory.\n", stderr); return 1; } project_path = find_project_root(); if (project_path == NULL) { fputs("Error: Cannot build project: Not in a project directory.\n", stderr); return 1; } project_manifest* manifest = load_manifest(text_format("%s/" MANIFEST_FNAME, project_path)); puts(buffer_text_format); // TODO: Validate dependencies. // Create build directory text_format("%s/build", project_path); char *build_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char)); strcpy(build_dir, buffer_text_format); text_format("%s/src", project_path); char *src_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char)); strcpy(src_dir, buffer_text_format); if (!file_exists(build_dir)) { bool status = makedir(build_dir); if (!status) { fputs("Error: Cannot build project: Cannot create build directory.\n", stderr); free_manifest(manifest); return 1; } } // Clear and populate. puts("Populating build directory..."); clear_directory(build_dir); copy_directory(src_dir, build_dir); if (file_exists(text_format("%s/libs", project_path))) copy_directory(src_dir, buffer_text_format); // Run scripts (if any) puts("Running scripts..."); // FIXME: This is incredibly hacky. text_format("%s/scripts", project_path); char *scripts_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char)); strcpy(scripts_dir, buffer_text_format); if (file_exists(scripts_dir)) { if (!run_scripts(scripts_dir)) { fputs("Failed running script!\b", stderr); return -1; } } if (options.zeal_build) { puts("Converting to ZealOS..."); convert_to_zealos(build_dir); } // TODO: Obfuscate code if enabled. text_format("%s/output", project_path); char *out_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char)); strcpy(out_dir, buffer_text_format); if (!file_exists(out_dir)) { bool status = makedir(out_dir); if (!status) { fputs("Error: Cannot build project: Cannot create output directory.\n", stderr); free_manifest(manifest); return 1; } } if (options.zeal_build) text_format("%s/output/%s-%s.zeal.ISO.C", project_path, manifest->name, manifest->version); else text_format("%s/output/%s-%s.ISO.C", project_path, manifest->name, manifest->version); char *iso_c = malloc((strlen(buffer_text_format)+1)*sizeof(char)); strcpy(iso_c, buffer_text_format); #if defined(_WIN32) system(text_format("%s/RedSeaGen.exe '%s' '%s'", abs_exe_path, build_dir, iso_c)); #else system(text_format("%s/RedSeaGen '%s' '%s'", abs_exe_path, build_dir, iso_c)); #endif free(build_dir); free_manifest(manifest); } }