88#pragma clang diagnostic push
99
1010#include < juce_gui_basics/juce_gui_basics.h>
11+ #include < xz/src/liblzma/api/lzma.h>
12+ #include < fstream>
1113#include " Constants.h"
1214
1315struct Toolchain {
@@ -148,12 +150,14 @@ class ToolchainInstaller final : public Component
148150 String downloadLocation = " https://github.com/plugdata-team/plugdata-heavy-toolchain/releases/download/v" + latestVersion + " /" ;
149151
150152#if JUCE_MAC
151- downloadLocation += " Heavy-MacOS-Universal.zip " ;
153+ downloadLocation += " Heavy-MacOS-Universal.tar.xz " ;
152154#elif JUCE_WINDOWS
153- downloadLocation += " Heavy-Win64.zip " ;
155+ downloadLocation += " Heavy-Win64.tar.xz " ;
154156#elif JUCE_LINUX && !__aarch64__
155- downloadLocation += " Heavy-Linux-x64.zip " ;
157+ downloadLocation += " Heavy-Linux-x64.tar.xz " ;
156158#endif
159+ // for testing, you can set a local archive path here
160+ // downloadLocation = "file://Users/timothy/Downloads/Heavy-MacOS-Universal.tar.xz";
157161
158162 instream = URL (downloadLocation).createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress).withConnectionTimeoutMs (10000 ).withStatusCode (&statusCode));
159163 startThread ();
@@ -259,17 +263,105 @@ class ToolchainInstaller final : public Component
259263
260264 startTimer (25 );
261265
262- MemoryInputStream input (toolchainData, false );
263- ZipFile zip (input);
264-
265266 auto const toolchainDir = ProjectInfo::appDataDir.getChildFile (" Toolchain" );
266267
267268 if (toolchainDir.exists ())
268269 toolchainDir.deleteRecursively ();
269270
270- auto const result = zip.uncompressTo (toolchainDir);
271+ HeapArray<uint8_t > decompressedToolchain;
272+ #if JUCE_LINUX || JUCE_WINDOWS
273+ decompressedToolchain.reserve (800 * 1024 * 1024 );
274+ #else
275+ decompressedToolchain.reserve (500 * 1024 * 1024 );
276+ #endif
277+ bool failed = false ;
278+ {
279+ lzma_stream strm = LZMA_STREAM_INIT;
280+ if (lzma_stream_decoder (&strm, UINT64_MAX, 0 ) != LZMA_OK) {
281+ failed = true ;
282+ }
283+ else {
284+ strm.next_in = reinterpret_cast <const uint8_t *>(toolchainData.getData ());
285+ strm.avail_in = toolchainData.getSize ();
286+
287+ uint8_t buffer[8192 ];
288+ lzma_ret ret;
289+
290+ do {
291+ strm.next_out = buffer;
292+ strm.avail_out = sizeof (buffer);
293+
294+ ret = lzma_code (&strm, LZMA_FINISH);
295+ size_t written = sizeof (buffer) - strm.avail_out ;
296+ decompressedToolchain.insert (decompressedToolchain.end (), buffer, buffer + written);
297+
298+ if (ret != LZMA_OK && ret != LZMA_STREAM_END) {
299+ failed = true ;
300+ lzma_end (&strm);
301+ }
302+ } while (ret != LZMA_STREAM_END);
303+
304+ lzma_end (&strm);
305+ }
306+ }
271307
272- if (!result.wasOk () || statusCode >= 400 ) {
308+ // Parse and extract .tar
309+ // TODO: we do this a few times inline, move it into a helper function along with the xz decompression
310+ auto extractTar = [](const uint8_t * data, size_t size, const File& destRoot) -> bool {
311+ size_t offset = 0 ;
312+ while (offset + 512 <= size) {
313+ const uint8_t * header = data + offset;
314+ if (header[0 ] == ' \0 ' ) break ; // End of archive
315+
316+ // Get file name
317+ std::string name (reinterpret_cast <const char *>(header), 100 );
318+ if (name.empty ()) break ;
319+
320+ #if !JUCE_WINDOWS
321+ mode_t mode = static_cast <mode_t >(
322+ std::strtoul (reinterpret_cast <const char *>(header + 100 ), nullptr , 8 )
323+ );
324+ bool executable = (mode & 0100 ) || (mode & 0010 ) || (mode & 0001 );
325+ #endif
326+ // Get file size (octal)
327+ size_t fileSize = std::strtoull (reinterpret_cast <const char *>(header + 124 ), nullptr , 8 );
328+ File outFile = destRoot.getChildFile (String (name));
329+
330+ // Determine type
331+ char typeFlag = header[156 ];
332+ if (typeFlag == ' 5' ) {
333+ outFile.createDirectory ();
334+ #if !JUCE_WINDOWS
335+ outFile.setExecutePermission (executable);
336+ #endif
337+ } else if (typeFlag == ' 0' || typeFlag == ' \0 ' ) {
338+ outFile.getParentDirectory ().createDirectory ();
339+ std::ofstream out (outFile.getFullPathName ().toRawUTF8 (), std::ios::binary);
340+ size_t fileOffset = offset + 512 ;
341+ out.write (reinterpret_cast <const char *>(data + fileOffset), fileSize);
342+
343+ if (!out.good ()) {
344+ out.close ();
345+ outFile.deleteFile (); // cleanup partial file
346+ return false ;
347+ }
348+ out.close ();
349+ #if !JUCE_WINDOWS
350+ outFile.setExecutePermission (executable);
351+ #endif
352+ }
353+
354+ size_t totalEntrySize = 512 + ((fileSize + 511 ) & ~511 ); // pad to next 512
355+ offset += totalEntrySize;
356+ }
357+ return true ;
358+ };
359+
360+ if (!extractTar (decompressedToolchain.data (), decompressedToolchain.size (), toolchainDir)) {
361+ failed = true ;
362+ }
363+
364+ if (failed || statusCode >= 400 ) {
273365 MessageManager::callAsync ([this ] {
274366 installButton.topText = " Try Again" ;
275367 errorMessage = " Error: Could not extract downloaded package" ;
@@ -278,28 +370,8 @@ class ToolchainInstaller final : public Component
278370 });
279371 return ;
280372 }
281-
282- // Make sure downloaded files have executable permission on unix
283- #if JUCE_MAC || JUCE_LINUX || JUCE_BSD
284-
285- auto const & tcPath = Toolchain::dir.getFullPathName ();
286- auto const permissionsScript = String (" #!/bin/bash" )
287- + " \n chmod +x " + tcPath + " /bin/Heavy/Heavy"
288- + " \n chmod +x " + tcPath + " /bin/*"
289- + " \n chmod +x " + tcPath + " /lib/dpf/utils/generate-ttl.sh"
290- + " \n chmod +x " + tcPath + " /arm-none-eabi/bin/*"
291- + " \n chmod +x " + tcPath + " /lib/gcc/arm-none-eabi/*/*"
292- + " \n chmod +x " + tcPath + " /lib/OwlProgram/Tools/*"
293- # if JUCE_LINUX
294- + " \n chmod +x " + tcPath + " /x86_64-anywhere-linux-gnu/bin/*"
295- + " \n chmod +x " + tcPath + " /x86_64-anywhere-linux-gnu/sysroot/sbin/*"
296- + " \n chmod +x " + tcPath + " /x86_64-anywhere-linux-gnu/sysroot/usr/bin/*"
297- # endif
298- ;
299-
300- Toolchain::startShellScript (permissionsScript);
301-
302- #elif JUCE_WINDOWS
373+
374+ #if JUCE_WINDOWS
303375 File usbDriverInstaller = Toolchain::dir.getChildFile (" etc" ).getChildFile (" usb_driver" ).getChildFile (" install-filter.exe" );
304376 File driverSpec = Toolchain::dir.getChildFile (" etc" ).getChildFile (" usb_driver" ).getChildFile (" DFU_in_FS_Mode.inf" );
305377
0 commit comments