mojic

Obfuscate C source code into e...
Log | Files | Refs | README | LICENSE

mojic.js (12773B)


      1 #!/usr/bin/env node
      2 
      3 import { program } from 'commander';
      4 import inquirer from 'inquirer';
      5 import chalk from 'chalk';
      6 import fs from 'fs';
      7 import path from 'path';
      8 import { Transform } from 'stream';
      9 import { StringDecoder } from 'string_decoder';
     10 import { CipherEngine } from '../lib/CipherEngine.js';
     11 
     12 const VERSION = '2.1.5';
     13 
     14 program
     15     .name('mojic')
     16     .description('Obfuscate C source code into emojis')
     17     .version(VERSION)
     18     .addHelpCommand('help [command]', 'Display help for command')
     19     .showHelpAfterError();
     20 
     21 // --- Helpers ---
     22 
     23 const promptPassword = async (msg) => {
     24     const { password } = await inquirer.prompt([{
     25         type: 'password', name: 'password', message: msg, mask: '*',
     26         validate: (input) => input.length > 5 || 'Password must be > 5 chars.'
     27     }]);
     28     return password;
     29 };
     30 
     31 const getStreamHeader = async (filePath) => {
     32     const fileStream = fs.createReadStream(filePath, { highWaterMark: 1024 });
     33     const decoder = new StringDecoder('utf8');
     34     
     35     return new Promise((resolve, reject) => {
     36         let buffer = '';
     37         let found = false;
     38         
     39         fileStream.on('data', (chunk) => {
     40             if (found) return;
     41             buffer += decoder.write(chunk);
     42             const newlineIndex = buffer.indexOf('\n');
     43             if (newlineIndex !== -1) {
     44                 found = true;
     45                 fileStream.destroy();
     46                 resolve(buffer.substring(0, newlineIndex));
     47             }
     48         });
     49         
     50         fileStream.on('end', () => {
     51             if (!found) resolve(buffer + decoder.end());
     52         });
     53         fileStream.on('error', reject);
     54     });
     55 };
     56 
     57 const createHeaderSkipper = () => {
     58     const decoder = new StringDecoder('utf8');
     59     let isHeaderSkipped = false;
     60     let buffer = '';
     61 
     62     return new Transform({
     63         transform(chunk, encoding, cb) {
     64             if (isHeaderSkipped) {
     65                 this.push(chunk);
     66                 return cb();
     67             }
     68             
     69             buffer += decoder.write(chunk);
     70             const nlIndex = buffer.indexOf('\n');
     71             
     72             if (nlIndex !== -1) {
     73                 const remainder = buffer.substring(nlIndex + 1);
     74                 this.push(remainder);
     75                 isHeaderSkipped = true;
     76                 buffer = '';
     77             }
     78             cb();
     79         }
     80     });
     81 };
     82 
     83 const createMinifier = () => {
     84     return new Transform({
     85         transform(chunk, encoding, cb) {
     86             let str = chunk.toString();
     87             str = str.replace(/\r?\n|\r/g, ' '); 
     88             str = str.replace(/\s+/g, ' ');      
     89             this.push(str);
     90             cb();
     91         }
     92     });
     93 };
     94 
     95 async function traverseDirectory(currentPath, extension, callback) {
     96     const entries = fs.readdirSync(currentPath, { withFileTypes: true });
     97     for (const entry of entries) {
     98         const fullPath = path.join(currentPath, entry.name);
     99         if (entry.isDirectory()) {
    100             await traverseDirectory(fullPath, extension, callback);
    101         } else if (entry.isFile() && entry.name.endsWith(extension)) {
    102             await callback(fullPath);
    103         }
    104     }
    105 }
    106 
    107 // --- Main Commands ---
    108 
    109 program
    110     .command('encode')
    111     .argument('<path>', 'File or Directory to encode')
    112     .option('-r, --recursive', 'Recursively encrypt all .c files in directory')
    113     .option('-f, --flat', 'Flatten structure (strip whitespace/newlines) before encrypting')
    114     .description('Encrypt a file or directory into emojis')
    115     .action(async (targetPath, options) => {
    116         try {
    117             if (!fs.existsSync(targetPath)) throw new Error('Path not found');
    118             const stats = fs.statSync(targetPath);
    119 
    120             if (stats.isDirectory() && !options.recursive) {
    121                 throw new Error(`'${targetPath}' is a directory. Use -r to process recursively.`);
    122             }
    123 
    124             console.log(chalk.blue(`Initiating Mojic Encryption v${VERSION}...`));
    125             if (options.flat) console.log(chalk.yellow('   -> Structural Flattening Enabled'));
    126 
    127             const password = await promptPassword('Create password for file(s):');
    128 
    129             const processFile = async (filePath) => {
    130                 const outputName = filePath.replace(/\.c$/, '') + '.mojic';
    131                 console.log(chalk.dim(`   Processing: ${path.basename(filePath)} -> ${path.basename(outputName)}`));
    132 
    133                 const engine = new CipherEngine(password);
    134                 await engine.init(); 
    135                 
    136                 const readStream = fs.createReadStream(filePath);
    137                 const writeStream = fs.createWriteStream(outputName);
    138                 
    139                 writeStream.write(engine._encodeHeader());
    140                 
    141                 await new Promise((resolve, reject) => {
    142                     let pipeline = readStream;
    143                     if (options.flat) pipeline = pipeline.pipe(createMinifier());
    144 
    145                     pipeline
    146                         .pipe(engine.getEncryptStream())
    147                         .pipe(writeStream)
    148                         .on('finish', resolve)
    149                         .on('error', reject);
    150                 });
    151             };
    152 
    153             if (stats.isDirectory()) {
    154                 console.log(chalk.blue(`Scanning directory: ${targetPath}`));
    155                 await traverseDirectory(targetPath, '.c', processFile);
    156                 console.log(chalk.green('Batch encryption complete.'));
    157             } else {
    158                 await processFile(targetPath);
    159                 console.log(chalk.green(`Encrypted.`));
    160             }
    161 
    162         } catch (err) {
    163             console.error(chalk.red('Error:'), err.message);
    164         }
    165     });
    166 
    167 program
    168     .command('decode')
    169     .argument('<path>', 'File or Directory to decode')
    170     .option('-r, --recursive', 'Recursively decode all .mojic files')
    171     .description('Restore mojic files to C source code')
    172     .action(async (targetPath, options) => {
    173         try {
    174             if (!fs.existsSync(targetPath)) throw new Error('Path not found');
    175             const stats = fs.statSync(targetPath);
    176 
    177             if (stats.isDirectory() && !options.recursive) {
    178                 throw new Error(`'${targetPath}' is a directory. Use -r to process recursively.`);
    179             }
    180 
    181             console.log(chalk.blue('Initiating Decryption...'));
    182             const password = await promptPassword('Enter password:');
    183 
    184             const processFile = async (filePath) => {
    185                 try {
    186                     const outputName = filePath.replace(/\.mojic$/, '') + '.restored.c';
    187                     console.log(chalk.dim(`   Restoring: ${path.basename(filePath)}`));
    188 
    189                     const headerStr = await getStreamHeader(filePath);
    190                     const metadata = CipherEngine.decodeHeader(headerStr);
    191                     const engine = new CipherEngine(password);
    192                     await engine.init(metadata.saltHex, metadata.authCheckHex);
    193 
    194                     const readStream = fs.createReadStream(filePath);
    195                     const writeStream = fs.createWriteStream(outputName);
    196                     
    197                     await new Promise((resolve, reject) => {
    198                         const decryptStream = engine.getDecryptStream();
    199                         decryptStream.on('error', (err) => {
    200                             readStream.destroy(); // Stop reading
    201                             writeStream.destroy(); // Stop writing
    202                             reject(err);
    203                         });
    204 
    205                         readStream
    206                             .pipe(createHeaderSkipper())
    207                             .pipe(decryptStream)
    208                             .pipe(writeStream)
    209                             .on('finish', resolve)
    210                             .on('error', reject);
    211                     });
    212                     return true;
    213                 } catch (e) {
    214                     const outputName = filePath.replace(/\.mojic$/, '') + '.restored.c';
    215                     // Wait for stream handles to release
    216                     setTimeout(() => { if (fs.existsSync(outputName)) try { fs.unlinkSync(outputName); } catch(ign){} }, 100);
    217                     
    218                     if (e.message === "WRONG_PASSWORD") {
    219                         console.log(chalk.red(`   Error: Incorrect Password`));
    220                     } else if (e.message === "FILE_TAMPERED") {
    221                         console.log(chalk.red(`   Error: File Tampered! Integrity check failed.`));
    222                     } else {
    223                         console.log(chalk.red(`   Error: ${e.message}`));
    224                     }
    225                     return false;
    226                 }
    227             };
    228 
    229             if (stats.isDirectory()) {
    230                 console.log(chalk.blue(`Scanning directory: ${targetPath}`));
    231                 await traverseDirectory(targetPath, '.mojic', processFile);
    232                 console.log(chalk.green('Batch decryption complete.'));
    233             } else {
    234                 const success = await processFile(targetPath);
    235                 if (success) console.log(chalk.green(`Restored.`));
    236             }
    237 
    238         } catch (err) {
    239             console.error(chalk.red('Error:'), err.message);
    240         }
    241     });
    242 
    243 // --- SRT ---
    244 const rotatePassword = async (file) => {
    245     try {
    246         if (!fs.existsSync(file)) throw new Error('File not found');
    247         console.log(chalk.yellow(`Rotating Password for ${path.basename(file)}...`));
    248 
    249         const headerStr = await getStreamHeader(file);
    250         const metadata = CipherEngine.decodeHeader(headerStr);
    251         const oldPass = await promptPassword('Enter CURRENT password:');
    252         
    253         const oldEngine = new CipherEngine(oldPass);
    254         await oldEngine.init(metadata.saltHex, metadata.authCheckHex);
    255 
    256         const newPass = await promptPassword('Enter NEW password:');
    257         const newEngine = new CipherEngine(newPass);
    258         await newEngine.init(); 
    259 
    260         const tempFile = file + '.tmp';
    261         const readStream = fs.createReadStream(file);
    262         const writeStream = fs.createWriteStream(tempFile);
    263 
    264         writeStream.write(newEngine._encodeHeader());
    265 
    266         await new Promise((resolve, reject) => {
    267             const decryptStream = oldEngine.getDecryptStream();
    268             decryptStream.on('error', reject);
    269             readStream.pipe(createHeaderSkipper()).pipe(decryptStream).pipe(newEngine.getEncryptStream()).pipe(writeStream).on('finish', resolve).on('error', reject);
    270         });
    271 
    272         fs.renameSync(tempFile, file);
    273         console.log(chalk.green(`Password updated.`));
    274         
    275     } catch (err) {
    276         if (fs.existsSync(file + '.tmp')) fs.unlinkSync(file + '.tmp');
    277         if (err.message === "WRONG_PASSWORD") console.error(chalk.red('Error: Incorrect Current Password'));
    278         else console.error(chalk.red('Error:'), err.message);
    279     }
    280 };
    281 
    282 const reEncrypt = async (file) => {
    283     try {
    284         if (!fs.existsSync(file)) throw new Error('File not found');
    285         console.log(chalk.yellow(`Re-shuffling Entropy for ${path.basename(file)}...`));
    286 
    287         const headerStr = await getStreamHeader(file);
    288         const metadata = CipherEngine.decodeHeader(headerStr);
    289         const password = await promptPassword('Enter password:');
    290         
    291         const oldEngine = new CipherEngine(password);
    292         await oldEngine.init(metadata.saltHex, metadata.authCheckHex);
    293 
    294         const newEngine = new CipherEngine(password);
    295         await newEngine.init(); 
    296 
    297         const tempFile = file + '.tmp';
    298         const readStream = fs.createReadStream(file);
    299         const writeStream = fs.createWriteStream(tempFile);
    300 
    301         writeStream.write(newEngine._encodeHeader());
    302 
    303         await new Promise((resolve, reject) => {
    304             const decryptStream = oldEngine.getDecryptStream();
    305             decryptStream.on('error', reject);
    306             readStream.pipe(createHeaderSkipper()).pipe(decryptStream).pipe(newEngine.getEncryptStream()).pipe(writeStream).on('finish', resolve).on('error', reject);
    307         });
    308 
    309         fs.renameSync(tempFile, file);
    310         console.log(chalk.green(`File re-encrypted.`));
    311 
    312     } catch (err) {
    313         if (fs.existsSync(file + '.tmp')) fs.unlinkSync(file + '.tmp');
    314         if (err.message === "WRONG_PASSWORD") console.error(chalk.red('Error: Incorrect Password'));
    315         else console.error(chalk.red('Error:'), err.message);
    316     }
    317 };
    318 
    319 program.command('srt').description('Security and Rotation Tools').option('--pass <file>', 'Update password').option('--re <file>', 'Re-encrypt').action(async (options) => {
    320     if (options.pass) await rotatePassword(options.pass);
    321     else if (options.re) await reEncrypt(options.re);
    322     else { console.log(chalk.yellow('Please specify an option: --pass <file> or --re <file>')); program.commands.find(c => c.name() === 'srt').help(); }
    323 });
    324 
    325 program.parse(process.argv);
© notamitgamer • Site Built: 2026-07-21 13:58:23 UTC • git-mirror commit: 1037f62 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror