commit ec4e8a4194db059e96dd15b04a0efaf4392041e3
parent 17d70aede505b5bc55915d51332c181204cfdef7
Author: Amit Dutta <amitdutta4255@gmail.com>
Date: Thu, 29 Jan 2026 13:22:49 +0530
v1.2.5
Diffstat:
6 files changed, 62 insertions(+), 115 deletions(-)
diff --git a/README.md b/README.md
@@ -1,4 +1,4 @@
-# Mojic v1.2.1
+# Mojic v1.2.5
> **Operation Polymorphic Chaos: Obfuscate C source code into a randomized, password-seeded stream of emojis.**
diff --git a/bin/mojic.js b/bin/mojic.js
@@ -12,7 +12,7 @@ import { CipherEngine } from '../lib/CipherEngine.js';
program
.name('mojic')
.description('Obfuscate C source code into emojis')
- .version('1.2.2')
+ .version('1.2.5')
.addHelpCommand('help [command]', 'Display help for command')
.showHelpAfterError();
@@ -78,7 +78,6 @@ const createHeaderSkipper = () => {
});
};
-// --- FLATTENING (Minifier) ---
const createMinifier = () => {
return new Transform({
transform(chunk, encoding, cb) {
@@ -91,11 +90,8 @@ const createMinifier = () => {
});
};
-// --- Recursive Logic ---
-
async function traverseDirectory(currentPath, extension, callback) {
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
-
for (const entry of entries) {
const fullPath = path.join(currentPath, entry.name);
if (entry.isDirectory()) {
@@ -123,7 +119,7 @@ program
throw new Error(`'${targetPath}' is a directory. Use -r to process recursively.`);
}
- console.log(chalk.blue('Initiating Mojic Encryption v1.1...'));
+ console.log(chalk.blue('Initiating Mojic Encryption v1.2...'));
if (options.flat) console.log(chalk.yellow(' -> Structural Flattening Enabled'));
const password = await promptPassword('Create password for file(s):');
@@ -142,10 +138,7 @@ program
await new Promise((resolve, reject) => {
let pipeline = readStream;
-
- if (options.flat) {
- pipeline = pipeline.pipe(createMinifier());
- }
+ if (options.flat) pipeline = pipeline.pipe(createMinifier());
pipeline
.pipe(engine.getEncryptStream())
@@ -193,10 +186,7 @@ program
const headerStr = await getStreamHeader(filePath);
const metadata = CipherEngine.decodeHeader(headerStr);
-
const engine = new CipherEngine(password);
-
- // Pass the Auth Check Hex (if available in new format)
await engine.init(metadata.saltHex, metadata.authCheckHex);
const readStream = fs.createReadStream(filePath);
@@ -204,8 +194,9 @@ program
await new Promise((resolve, reject) => {
const decryptStream = engine.getDecryptStream();
-
decryptStream.on('error', (err) => {
+ readStream.destroy(); // Stop reading
+ writeStream.destroy(); // Stop writing
reject(err);
});
@@ -216,19 +207,12 @@ program
.on('finish', resolve)
.on('error', reject);
});
-
- return true; // Success
+ return true;
} catch (e) {
const outputName = filePath.replace(/\.mojic$/, '') + '.restored.c';
+ // Wait for stream handles to release
+ setTimeout(() => { if (fs.existsSync(outputName)) try { fs.unlinkSync(outputName); } catch(ign){} }, 100);
- // Cleanup output file on error
- setTimeout(() => {
- if (fs.existsSync(outputName)) {
- try { fs.unlinkSync(outputName); } catch(ign){}
- }
- }, 100);
-
- // Specific Error Messaging (No Emojis)
if (e.message === "WRONG_PASSWORD") {
console.log(chalk.red(` Error: Incorrect Password`));
} else if (e.message === "FILE_TAMPERED") {
@@ -236,8 +220,7 @@ program
} else {
console.log(chalk.red(` Error: ${e.message}`));
}
-
- return false; // Failure
+ return false;
}
};
@@ -247,9 +230,7 @@ program
console.log(chalk.green('Batch decryption complete.'));
} else {
const success = await processFile(targetPath);
- if (success) {
- console.log(chalk.green(`Restored.`));
- }
+ if (success) console.log(chalk.green(`Restored.`));
}
} catch (err) {
@@ -258,7 +239,6 @@ program
});
// --- SRT ---
-
const rotatePassword = async (file) => {
try {
if (!fs.existsSync(file)) throw new Error('File not found');
@@ -283,16 +263,8 @@ const rotatePassword = async (file) => {
await new Promise((resolve, reject) => {
const decryptStream = oldEngine.getDecryptStream();
-
- decryptStream.on('error', (err) => reject(err));
-
- readStream
- .pipe(createHeaderSkipper())
- .pipe(decryptStream)
- .pipe(newEngine.getEncryptStream())
- .pipe(writeStream)
- .on('finish', resolve)
- .on('error', reject);
+ decryptStream.on('error', reject);
+ readStream.pipe(createHeaderSkipper()).pipe(decryptStream).pipe(newEngine.getEncryptStream()).pipe(writeStream).on('finish', resolve).on('error', reject);
});
fs.renameSync(tempFile, file);
@@ -300,11 +272,8 @@ const rotatePassword = async (file) => {
} catch (err) {
if (fs.existsSync(file + '.tmp')) fs.unlinkSync(file + '.tmp');
- if (err.message === "WRONG_PASSWORD") {
- console.error(chalk.red('Error: Incorrect Current Password'));
- } else {
- console.error(chalk.red('Error:'), err.message);
- }
+ if (err.message === "WRONG_PASSWORD") console.error(chalk.red('Error: Incorrect Current Password'));
+ else console.error(chalk.red('Error:'), err.message);
}
};
@@ -331,15 +300,8 @@ const reEncrypt = async (file) => {
await new Promise((resolve, reject) => {
const decryptStream = oldEngine.getDecryptStream();
- decryptStream.on('error', (err) => reject(err));
-
- readStream
- .pipe(createHeaderSkipper())
- .pipe(decryptStream)
- .pipe(newEngine.getEncryptStream())
- .pipe(writeStream)
- .on('finish', resolve)
- .on('error', reject);
+ decryptStream.on('error', reject);
+ readStream.pipe(createHeaderSkipper()).pipe(decryptStream).pipe(newEngine.getEncryptStream()).pipe(writeStream).on('finish', resolve).on('error', reject);
});
fs.renameSync(tempFile, file);
@@ -347,28 +309,15 @@ const reEncrypt = async (file) => {
} catch (err) {
if (fs.existsSync(file + '.tmp')) fs.unlinkSync(file + '.tmp');
- if (err.message === "WRONG_PASSWORD") {
- console.error(chalk.red('Error: Incorrect Password'));
- } else {
- console.error(chalk.red('Error:'), err.message);
- }
+ if (err.message === "WRONG_PASSWORD") console.error(chalk.red('Error: Incorrect Password'));
+ else console.error(chalk.red('Error:'), err.message);
}
};
-program
- .command('srt')
- .description('Security and Rotation Tools')
- .option('--pass <file>', 'Update the password for an existing file')
- .option('--re <file>', 'Re-encrypt with new random seed (Same password)')
- .action(async (options) => {
- if (options.pass) {
- await rotatePassword(options.pass);
- } else if (options.re) {
- await reEncrypt(options.re);
- } else {
- console.log(chalk.yellow('Please specify an option: --pass <file> or --re <file>'));
- program.commands.find(c => c.name() === 'srt').help();
- }
- });
+program.command('srt').description('Security and Rotation Tools').option('--pass <file>', 'Update password').option('--re <file>', 'Re-encrypt').action(async (options) => {
+ if (options.pass) await rotatePassword(options.pass);
+ else if (options.re) await reEncrypt(options.re);
+ else { console.log(chalk.yellow('Please specify an option: --pass <file> or --re <file>')); program.commands.find(c => c.name() === 'srt').help(); }
+});
program.parse(process.argv);
\ No newline at end of file
diff --git a/lib/CipherEngine.js b/lib/CipherEngine.js
@@ -3,15 +3,12 @@ import { Transform } from 'stream';
import { StringDecoder } from 'string_decoder';
/**
- * MOJIC v1.2.1 CIPHER ENGINE
+ * MOJIC v1.2.5 CIPHER ENGINE
* "Operation Polymorphic Chaos"
- * * Features:
- * - Xoshiro256** PRNG (256-bit State)
- * - Base-1024 Compression (5 bytes -> 4 emojis)
- * - Polymorphic Keyword Encryption (Rolling Cipher)
- * - HMAC-SHA256 Integrity Footer
- * - Line Wrapping (New: Prevents lag in editors)
- * - Instant Password Verification (New: Header Auth Check)
+ * * Fixes:
+ * - Word Boundaries: Prevents splitting variables like 'secretCode'
+ * - Regex: correctly handles #directives vs keywords
+ * - Tokenization: Handles combined graphemes (skin tones/modifiers) during decryption
*/
// --- EMOJI UNIVERSE GENERATION ---
@@ -21,7 +18,7 @@ const generateUniverse = () => {
const universe = [];
const ranges = [
[0x1F600, 0x1F64F], // Emoticons
- [0x1F300, 0x1F5FF], // Misc Symbols
+ [0x1F300, 0x1F5FF], // Misc Symbols (Contains modifiers)
[0x1F680, 0x1F6FF], // Transport
[0x1F900, 0x1F9FF] // Supplemental
];
@@ -158,7 +155,7 @@ export class CipherEngine {
_encodeHeader() {
const saltHex = this.salt.toString('hex');
- const authCheck = this.authKey.subarray(0, 4).toString('hex'); // 4 bytes check
+ const authCheck = this.authKey.subarray(0, 4).toString('hex');
let headerStr = '';
for (const char of (saltHex + authCheck)) {
@@ -196,9 +193,12 @@ export class CipherEngine {
transform(chunk, encoding, callback) {
const str = chunk.toString('utf8');
- const sortedKeywords = [...C_KEYWORDS].sort((a, b) => b.length - a.length);
- const keywordPattern = sortedKeywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
- const regex = new RegExp(`(${keywordPattern})`, 'g');
+ // Separate alpha keywords (int, void) from symbols (#include)
+ const alphaKeywords = C_KEYWORDS.filter(k => /^\w+$/.test(k)).sort((a,b)=>b.length-a.length).join('|');
+ const symKeywords = C_KEYWORDS.filter(k => !/^\w+$/.test(k)).map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
+
+ // Match: \b(int|void)\b OR (#include)
+ const regex = new RegExp(`(\\b(?:${alphaKeywords})\\b|(?:${symKeywords}))`, 'g');
const parts = str.split(regex);
@@ -218,8 +218,6 @@ export class CipherEngine {
const outBuf = Buffer.from(polyEmoji);
engine.hmac.update(outBuf);
-
- // Emit wrapped
this.push(engine._wrapOutput(outBuf));
} else {
@@ -256,7 +254,6 @@ export class CipherEngine {
footerStr += HEADER_ALPHABET[val];
}
}
- // Push footer (always starts on a new line or wrapped)
this.push(Buffer.from('\n' + footerStr));
callback();
}
@@ -289,7 +286,6 @@ export class CipherEngine {
_flushDataBuffer(buf) {
if (buf.length === 0) return Buffer.alloc(0);
-
const padded = Buffer.alloc(5);
buf.copy(padded);
const enc = this._encodeBase1024(padded);
@@ -313,15 +309,22 @@ export class CipherEngine {
const segments = [...segmenter.segment(str)];
for (const { segment } of segments) {
- // Filter out whitespace/newlines used for wrapping
- if (segment.match(/\s/)) continue;
-
- emojiBuffer.push(segment);
-
- if (emojiBuffer.length > FOOTER_LEN) {
- const emo = emojiBuffer.shift();
- engine._processDecryptToken(emo, this);
- engine.hmac.update(Buffer.from(emo));
+ if (segment.match(/\s/)) continue; // Skip wraps
+
+ // FIX: Some emojis in the universe (like skin tones 0x1F3FB) are modifiers.
+ // If they appear next to another emoji, Intl.Segmenter merges them.
+ // We must split them back into atomic code points to preserve token count.
+ const atoms = [...segment];
+
+ for (const atom of atoms) {
+ emojiBuffer.push(atom);
+
+ if (emojiBuffer.length > FOOTER_LEN) {
+ const emo = emojiBuffer.shift();
+ engine._processDecryptToken(emo, this);
+ // Feed actual atomic tokens to HMAC to match encryption
+ engine.hmac.update(Buffer.from(emo));
+ }
}
}
callback();
@@ -349,13 +352,9 @@ export class CipherEngine {
}
if (footerHex !== calcDigest) {
- // Critical security failure
this.emit('error', new Error("FILE_TAMPERED"));
return;
}
-
- if (engine.decodeDataBuf.length > 0) {
- }
callback();
}
});
@@ -374,6 +373,7 @@ export class CipherEngine {
const originalEmo = this.keywordEmojis[baseIdx];
const keyword = this.keywordReverseMap.get(originalEmo);
+ // Safety: This buffer should be empty if the stream is synced.
if (this.decodeDataBuf.length > 0) {
this.decodeDataBuf = [];
}
@@ -388,8 +388,6 @@ export class CipherEngine {
const cleanChunk = chunk.filter(b => b !== 0x00);
stream.push(cleanChunk);
}
- } else {
- // Unknown emojis are ignored now
}
}
diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json
@@ -1,6 +1,6 @@
{
- "name": "@notamitgamer/mojic",
- "version": "1.2.2",
+ "name": "mojic",
+ "version": "1.2.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
diff --git a/package-lock.json b/package-lock.json
@@ -1,12 +1,12 @@
{
- "name": "@notamitgamer/mojic",
- "version": "1.2.2",
+ "name": "mojic",
+ "version": "1.2.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "@notamitgamer/mojic",
- "version": "1.2.2",
+ "name": "mojic",
+ "version": "1.2.4",
"license": "Apache-2.0",
"dependencies": {
"chalk": "^5.3.0",
diff --git a/package.json b/package.json
@@ -1,6 +1,6 @@
{
- "name": "@notamitgamer/mojic",
- "version": "1.2.2",
+ "name": "mojic",
+ "version": "1.2.5",
"description": "Obfuscate C source code into encrypted, password-seeded emoji streams.",
"main": "bin/mojic.js",
"bin": {