1+ // main.js
2+
13const { app, BrowserWindow, ipcMain } = require ( 'electron' ) ;
24const path = require ( 'path' ) ;
35const fs = require ( 'fs' ) ;
46const { spawn } = require ( 'child_process' ) ;
5- const mainIndex = path . join ( __dirname , 'index.js' )
6-
7- const createWindow = ( ) => {
8- const win = new BrowserWindow ( {
9- width : 800 ,
10- height : 600 ,
11- webPreferences : {
12- preload : path . join ( __dirname , 'ui' , 'externalScripts' , 'main_preload.js' )
13- }
14- } ) ;
157
16- win . loadFile ( path . join ( __dirname , 'ui' , 'chrome' , 'index.html' ) ) ;
8+ // New dependencies for reading mod metadata
9+ const AdmZip = require ( 'adm-zip' ) ;
10+ const toml = require ( '@iarna/toml' ) ;
11+
12+ // Path to your non-UI entry point
13+ const mainIndex = path . join ( __dirname , 'index.js' ) ;
14+
15+ function createWindow ( ) {
16+ const win = new BrowserWindow ( {
17+ width : 800 ,
18+ height : 600 ,
19+ webPreferences : {
20+ preload : path . join ( __dirname , 'ui' , 'externalScripts' , 'main_preload.js' ) ,
21+ } ,
22+ } ) ;
23+
24+ win . loadFile ( path . join ( __dirname , 'ui' , 'chrome' , 'index.html' ) ) ;
25+ }
26+
27+ function createConsole ( ) {
28+ const consoleWin = new BrowserWindow ( {
29+ width : 800 ,
30+ height : 600 ,
31+ webPreferences : {
32+ preload : path . join ( __dirname , 'ui' , 'externalScripts' , 'console_preload.js' ) ,
33+ } ,
34+ } ) ;
35+
36+ consoleWin . loadFile ( path . join ( __dirname , 'ui' , 'chrome' , 'console.html' ) ) ;
37+ return consoleWin ;
1738}
1839
19- const createConsole = ( ) => {
20- const consoleWin = new BrowserWindow ( {
21- width : 800 ,
22- height : 600 ,
23- webPreferences : {
24- preload : path . join ( __dirname , 'ui' , 'externalScripts' , 'console_preload.js' )
25- }
40+ /**
41+ * Extracts NeoForge/Fabric mod metadata from a .jar/.zip
42+ * Priorities:
43+ * 1. META-INF/neoforge.mods.toml
44+ * 2. META-INF/mods.toml (legacy)
45+ * 3. fabric.mod.json
46+ * 4. META-INF/MANIFEST.MF
47+ */
48+ function getModMetadata ( jarPath ) {
49+ const zip = new AdmZip ( jarPath ) ;
50+
51+ // 1. NeoForge TOML
52+ let entry = zip . getEntry ( 'META-INF/neoforge.mods.toml' ) ;
53+ if ( entry ) {
54+ const raw = entry . getData ( ) . toString ( 'utf8' ) ;
55+ const parsed = toml . parse ( raw ) ;
56+ return parsed . mods . map ( mod => ( {
57+ id : mod . modId ,
58+ version : mod . version ,
59+ name : mod . displayName ,
60+ description : mod . description ,
61+ authors : mod . authors ,
62+ dependencies : mod . dependencies || { } ,
63+ } ) ) ;
64+ }
65+
66+ // 2. Legacy mods.toml
67+ entry = zip . getEntry ( 'META-INF/mods.toml' ) ;
68+ if ( entry ) {
69+ const raw = entry . getData ( ) . toString ( 'utf8' ) ;
70+ const parsed = toml . parse ( raw ) ;
71+ return parsed . mods . map ( mod => ( {
72+ id : mod . modId ,
73+ version : mod . version ,
74+ name : mod . displayName ,
75+ description : mod . description ,
76+ authors : mod . authors ,
77+ dependencies : mod . dependencies || { } ,
78+ } ) ) ;
79+ }
80+
81+ // 3. Fabric JSON
82+ entry = zip . getEntry ( 'fabric.mod.json' ) ;
83+ if ( entry ) {
84+ const json = JSON . parse ( entry . getData ( ) . toString ( 'utf8' ) ) ;
85+ return [ {
86+ id : json . id ,
87+ version : json . version ,
88+ name : ( json . metadata && json . metadata . name ) || json . id ,
89+ description : json . metadata && json . metadata . description ,
90+ authors : ( json . metadata && json . metadata . contributors || [ ] ) . map ( c => c . name ) ,
91+ dependencies : json . depends || { } ,
92+ } ] ;
93+ }
94+
95+ // 4. Manifest fallback
96+ entry = zip . getEntry ( 'META-INF/MANIFEST.MF' ) ;
97+ if ( entry ) {
98+ const text = entry . getData ( ) . toString ( 'utf8' ) ;
99+ const props = { } ;
100+ text . split ( / \r ? \n / ) . forEach ( line => {
101+ const [ k , v ] = line . split ( ': ' ) ;
102+ if ( k && v ) props [ k . trim ( ) ] = v . trim ( ) ;
26103 } ) ;
104+ return [ {
105+ id : props [ 'Implementation-Title' ] || path . basename ( jarPath ) ,
106+ version : props [ 'Implementation-Version' ] || 'unknown' ,
107+ } ] ;
108+ }
27109
28- consoleWin . loadFile ( path . join ( __dirname , 'ui' , 'chrome' , 'console.html' ) ) ;
29- return consoleWin
110+ // No metadata found
111+ return [ ] ;
30112}
31113
32114app . whenReady ( ) . then ( ( ) => {
33- ipcMain . on ( 'launch' , ( event , arg ) => {
34- let consoleWin = createConsole ( ) ;
35- const process = spawn ( 'node' , [ mainIndex , "--ui" ] ) ;
36-
37- process . stdout . on ( 'data' , ( data ) => {
38- consoleWin . webContents . send ( 'msg' , String ( data ) ) ; // Send to renderer
39- console . log ( `stdout: ${ data } ` ) ;
40- } ) ;
41-
42- process . stderr . on ( 'data' , ( data ) => {
43- consoleWin . webContents . send ( 'error' , String ( data ) ) ; // Send to renderer
44- console . error ( `stderr: ${ data } ` ) ;
45- } ) ;
46-
47- process . on ( 'close' , ( code ) => {
48- console . log ( `child process exited with code ${ code } ` ) ;
49- } ) ;
50-
51- process . on ( 'error' , ( error ) => {
52- console . error ( `Error: ${ error } ` ) ;
53- } ) ;
54-
55- process . on ( 'exit' , ( code ) => {
56- console . log ( `Process exited with code: ${ code } ` ) ;
57- event . reply ( 'process-exit' , code ) ;
58- consoleWin . close ( ) ;
59- consoleWin = null ;
60- } ) ;
115+ // Launch handler (existing)
116+ ipcMain . on ( 'launch' , ( event , arg ) => {
117+ let consoleWin = createConsole ( ) ;
118+ const proc = spawn ( 'node' , [ mainIndex , '--ui' ] ) ;
119+
120+ proc . stdout . on ( 'data' , data => {
121+ consoleWin . webContents . send ( 'msg' , data . toString ( ) ) ;
122+ console . log ( `stdout: ${ data } ` ) ;
61123 } ) ;
62- ipcMain . on ( "downloadToModsFolder" , ( event , url ) => {
63- fetch ( url )
64- . then ( response => {
65- if ( ! response . ok ) {
66- throw new Error ( 'Network response was not ok' ) ;
67- }
68- return response . arrayBuffer ( ) ;
69- } )
70- . then ( buffer => {
71- const modsFolder = path . join ( __dirname , ".minecraft" , 'mods' ) ;
72- if ( ! fs . existsSync ( modsFolder ) ) {
73- fs . mkdirSync ( modsFolder ) ;
74- }
75- const filePath = path . join ( modsFolder , decodeURIComponent ( path . basename ( url ) ) ) ;
76- fs . writeFileSync ( filePath , Buffer . from ( buffer ) ) ;
77- event . reply ( 'download-complete' , filePath ) ;
78- } )
124+
125+ proc . stderr . on ( 'data' , data => {
126+ consoleWin . webContents . send ( 'error' , data . toString ( ) ) ;
127+ console . error ( `stderr: ${ data } ` ) ;
79128 } ) ;
80- createWindow ( ) ;
81- } ) ;
129+
130+ proc . on ( 'close' , code => {
131+ console . log ( `child process exited with code ${ code } ` ) ;
132+ } ) ;
133+
134+ proc . on ( 'error' , error => {
135+ console . error ( `Error: ${ error } ` ) ;
136+ } ) ;
137+
138+ proc . on ( 'exit' , code => {
139+ console . log ( `Process exited with code: ${ code } ` ) ;
140+ event . reply ( 'process-exit' , code ) ;
141+ consoleWin . close ( ) ;
142+ consoleWin = null ;
143+ } ) ;
144+ } ) ;
145+
146+ // Download-to-mods-folder handler (existing)
147+ ipcMain . on ( 'downloadToModsFolder' , ( event , url ) => {
148+ fetch ( url )
149+ . then ( response => {
150+ if ( ! response . ok ) throw new Error ( 'Network response was not ok' ) ;
151+ return response . arrayBuffer ( ) ;
152+ } )
153+ . then ( buffer => {
154+ const modsFolder = path . join ( __dirname , '.minecraft' , 'mods' ) ;
155+ if ( ! fs . existsSync ( modsFolder ) ) fs . mkdirSync ( modsFolder ) ;
156+ const filePath = path . join ( modsFolder , decodeURIComponent ( path . basename ( url ) ) ) ;
157+ fs . writeFileSync ( filePath , Buffer . from ( buffer ) ) ;
158+ event . reply ( 'download-complete' , filePath ) ;
159+ } )
160+ . catch ( err => {
161+ event . reply ( 'download-error' , err . message ) ;
162+ } ) ;
163+ } ) ;
164+
165+ // List raw mod files (existing)
166+ ipcMain . handle ( 'getInstalledMods' , ( ) => {
167+ const modsFolder = path . join ( __dirname , '.minecraft' , 'mods' ) ;
168+ if ( ! fs . existsSync ( modsFolder ) ) fs . mkdirSync ( modsFolder ) ;
169+ return fs . readdirSync ( modsFolder )
170+ . filter ( f => f . endsWith ( '.jar' ) || f . endsWith ( '.zip' ) )
171+ . map ( f => path . join ( modsFolder , f ) ) ;
172+ } ) ;
173+
174+ // New: List mods with parsed metadata
175+ ipcMain . handle ( 'getModsWithMetadata' , ( ) => {
176+ const modsFolder = path . join ( __dirname , '.minecraft' , 'mods' ) ;
177+ if ( ! fs . existsSync ( modsFolder ) ) fs . mkdirSync ( modsFolder ) ;
178+
179+ return fs . readdirSync ( modsFolder )
180+ . filter ( f => f . endsWith ( '.jar' ) || f . endsWith ( '.zip' ) )
181+ . map ( filename => {
182+ const fullPath = path . join ( modsFolder , filename ) ;
183+ const metadata = getModMetadata ( fullPath ) ;
184+ return { path : fullPath , metadata } ;
185+ } ) ;
186+ } ) ;
187+
188+ ipcMain . on ( 'deleteFromModsFolder' , ( event , path ) => {
189+ fs . rmSync ( path , { force : true } ) ;
190+ } ) ;
191+
192+ // Open main window
193+ createWindow ( ) ;
194+ } ) ;
195+
196+ // Quit on all windows closed (optional, standard behavior)
197+ app . on ( 'window-all-closed' , ( ) => {
198+ if ( process . platform !== 'darwin' ) app . quit ( ) ;
199+ } ) ;
0 commit comments