Reaperxxxx commited on
Commit
864fe49
Β·
verified Β·
1 Parent(s): fa2a298

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +46 -24
index.js CHANGED
@@ -6,10 +6,15 @@ const fs = require('fs');
6
  const os = require('os');
7
  const { execSync, spawnSync } = require('child_process');
8
  const axios = require('axios');
 
9
 
10
  const app = express();
11
  const PORT = 3000;
12
 
 
 
 
 
13
  app.use(express.json({ limit: '200mb' }));
14
  app.use(express.static(path.join(__dirname, 'public')));
15
 
@@ -62,6 +67,22 @@ function checkTools() {
62
 
63
  // ── ROUTES ───────────────────────────────────────────────────────────────────
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  // GET /tools β€” let frontend know what's available
66
  app.get('/tools', (req, res) => {
67
  res.json(checkTools());
@@ -69,7 +90,9 @@ app.get('/tools', (req, res) => {
69
 
70
  // GET /session/:token β€” check if a session is still alive
71
  app.get('/session/:token', (req, res) => {
72
- const sessionDir = path.join(os.tmpdir(), req.params.token);
 
 
73
  const alive = fs.existsSync(path.join(sessionDir, 'original.apk'));
74
  res.json({ alive });
75
  });
@@ -91,13 +114,25 @@ app.get('/debug', (req, res) => {
91
  });
92
 
93
  // GET /init β€” load bundled APK from disk, no upload needed
94
- const BUNDLED_APK = path.join(__dirname, 'super-city-2.010.64-mod-t-5play.apk');
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  app.get('/init', (req, res) => {
97
  if (!fs.existsSync(BUNDLED_APK)) {
98
  return res.status(404).json({ error: 'Bundled APK not found on server' });
99
  }
100
  try {
 
101
  const zip = new AdmZip(BUNDLED_APK);
102
  const entry = zip.getEntries().find(e =>
103
  e.entryName.replace(/\\/g, '/').toLowerCase() === 'assets/characters.txt'
@@ -106,13 +141,9 @@ app.get('/init', (req, res) => {
106
  const names = zip.getEntries().map(e => e.entryName).join(', ');
107
  return res.status(400).json({ error: `assets/characters.txt not found. Entries: ${names}` });
108
  }
109
-
110
- const content = entry.getData().toString('utf8');
111
- const token = Date.now() + '_' + Math.random().toString(36).slice(2);
112
- const sessionDir = path.join(os.tmpdir(), token);
113
- fs.mkdirSync(sessionDir);
114
- fs.copyFileSync(BUNDLED_APK, path.join(sessionDir, 'original.apk'));
115
- fs.writeFileSync(path.join(sessionDir, 'original_name.txt'), path.basename(BUNDLED_APK));
116
  res.json({ token, characters: content });
117
  } catch (err) {
118
  res.status(500).json({ error: err.message });
@@ -248,20 +279,11 @@ app.post('/patch', express.json({ limit: '10mb' }), (req, res) => {
248
  throw new Error('No signing tool found. Install JDK (keytool+jarsigner) or Android SDK build-tools (apksigner+zipalign) and make sure they are on PATH.');
249
  }
250
 
251
- // 5. Stream signed APK back
252
- const origName = fs.existsSync(path.join(sessionDir, 'original_name.txt'))
253
- ? fs.readFileSync(path.join(sessionDir, 'original_name.txt'), 'utf8').trim()
254
- : 'SuperCity.apk';
255
- const outName = origName.replace(/\.apk$/i, '_modded.apk');
256
-
257
- res.setHeader('Content-Disposition', `attachment; filename="${outName}"`);
258
- res.setHeader('Content-Type', 'application/vnd.android.package-archive');
259
- const stream = fs.createReadStream(signedApk);
260
- stream.pipe(res);
261
- stream.on('end', () => {
262
- // Cleanup session after download
263
- try { fs.rmSync(sessionDir, { recursive: true, force: true }); } catch {}
264
- });
265
 
266
  } catch (err) {
267
  console.error(err);
@@ -277,6 +299,6 @@ app.get('/', (req, res) => {
277
  // ── START ─────────────────────────────────────────────────────────────────────
278
  app.listen(7860, () => {
279
  console.log(`\n🦸 Super City Editor backend running at http://localhost:${PORT}`);
280
- console.log('Open that URL in your browser to use the editor.\n');
281
  try { ensureKeystore(); } catch (e) { console.warn('Keystore not ready yet:', e.message); }
282
  });
 
6
  const os = require('os');
7
  const { execSync, spawnSync } = require('child_process');
8
  const axios = require('axios');
9
+ const crypto = require('crypto');
10
 
11
  const app = express();
12
  const PORT = 3000;
13
 
14
+ // ── DOWNLOAD HASH ─────────────────────────────────────────────────────────────
15
+ // A stable hash generated once at startup. Share this URL to download the APK.
16
+ const DOWNLOAD_HASH = crypto.randomBytes(16).toString('hex');
17
+
18
  app.use(express.json({ limit: '200mb' }));
19
  app.use(express.static(path.join(__dirname, 'public')));
20
 
 
67
 
68
  // ── ROUTES ───────────────────────────────────────────────────────────────────
69
 
70
+ // GET /download-link β€” returns the current download URL
71
+ app.get('/download-link', (req, res) => {
72
+ const patched = fs.existsSync(path.join(__dirname, 'supercity_patched.apk'));
73
+ res.json({ url: `/download/${DOWNLOAD_HASH}`, patched });
74
+ });
75
+
76
+ // GET /download/:hash β€” serve the patched APK only
77
+ app.get('/download/:hash', (req, res) => {
78
+ if (req.params.hash !== DOWNLOAD_HASH) return res.status(403).json({ error: 'Invalid download link' });
79
+ const patched = path.join(__dirname, 'supercity_patched.apk');
80
+ if (!fs.existsSync(patched)) return res.status(404).json({ error: 'No patched APK yet β€” edit and export first' });
81
+ res.setHeader('Content-Disposition', 'attachment; filename="supercity_modded.apk"');
82
+ res.setHeader('Content-Type', 'application/vnd.android.package-archive');
83
+ fs.createReadStream(patched).pipe(res);
84
+ });
85
+
86
  // GET /tools β€” let frontend know what's available
87
  app.get('/tools', (req, res) => {
88
  res.json(checkTools());
 
90
 
91
  // GET /session/:token β€” check if a session is still alive
92
  app.get('/session/:token', (req, res) => {
93
+ const sessionDir = req.params.token === 'persistent'
94
+ ? PERSISTENT_DIR
95
+ : path.join(os.tmpdir(), req.params.token);
96
  const alive = fs.existsSync(path.join(sessionDir, 'original.apk'));
97
  res.json({ alive });
98
  });
 
114
  });
115
 
116
  // GET /init β€” load bundled APK from disk, no upload needed
117
+ const BUNDLED_APK = path.join(__dirname, 'super-city-2.010.64-mod-t-5play.apk');
118
+ const PERSISTENT_DIR = path.join(os.tmpdir(), 'supercity-persistent');
119
+
120
+ // Ensure persistent session dir exists with the APK linked/copied once
121
+ function ensurePersistentSession() {
122
+ if (!fs.existsSync(PERSISTENT_DIR)) fs.mkdirSync(PERSISTENT_DIR, { recursive: true });
123
+ const dest = path.join(PERSISTENT_DIR, 'original.apk');
124
+ if (!fs.existsSync(dest)) {
125
+ fs.copyFileSync(BUNDLED_APK, dest);
126
+ fs.writeFileSync(path.join(PERSISTENT_DIR, 'original_name.txt'), path.basename(BUNDLED_APK));
127
+ }
128
+ }
129
 
130
  app.get('/init', (req, res) => {
131
  if (!fs.existsSync(BUNDLED_APK)) {
132
  return res.status(404).json({ error: 'Bundled APK not found on server' });
133
  }
134
  try {
135
+ ensurePersistentSession();
136
  const zip = new AdmZip(BUNDLED_APK);
137
  const entry = zip.getEntries().find(e =>
138
  e.entryName.replace(/\\/g, '/').toLowerCase() === 'assets/characters.txt'
 
141
  const names = zip.getEntries().map(e => e.entryName).join(', ');
142
  return res.status(400).json({ error: `assets/characters.txt not found. Entries: ${names}` });
143
  }
144
+ const content = entry.getData().toString('utf8');
145
+ // Use a stable token derived from the APK filename β€” same across page reloads
146
+ const token = 'persistent';
 
 
 
 
147
  res.json({ token, characters: content });
148
  } catch (err) {
149
  res.status(500).json({ error: err.message });
 
279
  throw new Error('No signing tool found. Install JDK (keytool+jarsigner) or Android SDK build-tools (apksigner+zipalign) and make sure they are on PATH.');
280
  }
281
 
282
+ // 5. Save to fixed output path and return download URL
283
+ const PATCHED_OUTPUT = path.join(__dirname, 'supercity_patched.apk');
284
+ fs.copyFileSync(signedApk, PATCHED_OUTPUT);
285
+ try { fs.rmSync(sessionDir, { recursive: true, force: true }); } catch {}
286
+ res.json({ url: `/download/${DOWNLOAD_HASH}` });
 
 
 
 
 
 
 
 
 
287
 
288
  } catch (err) {
289
  console.error(err);
 
299
  // ── START ─────────────────────────────────────────────────────────────────────
300
  app.listen(7860, () => {
301
  console.log(`\n🦸 Super City Editor backend running at http://localhost:${PORT}`);
302
+ console.log(`\n⬇ Direct APK download: http://localhost:${PORT}/download/${DOWNLOAD_HASH}\n`);
303
  try { ensureKeystore(); } catch (e) { console.warn('Keystore not ready yet:', e.message); }
304
  });