1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // Multi-Phasic Applications: SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the GNU General Public License v3+, or later.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc
.squirreljme
.plugin
.multivm
;
12 import cc
.squirreljme
.plugin
.multivm
.ident
.SourceTargetClassifier
;
13 import java
.io
.IOException
;
14 import java
.io
.InputStream
;
15 import java
.io
.PrintStream
;
16 import java
.nio
.charset
.StandardCharsets
;
17 import java
.nio
.file
.Files
;
18 import java
.nio
.file
.Path
;
19 import java
.nio
.file
.StandardCopyOption
;
20 import java
.nio
.file
.StandardOpenOption
;
21 import java
.security
.MessageDigest
;
22 import java
.security
.NoSuchAlgorithmException
;
23 import java
.util
.Arrays
;
24 import org
.gradle
.api
.Action
;
25 import org
.gradle
.api
.Task
;
28 * This is the action that generates the built-in ROM for RatufaCoat.
32 public class RatufaCoatBuiltInTaskAction
33 implements Action
<Task
>
35 /** The column limit. */
36 private static final int _COLS
=
39 /** Compact writing table, for the quickest and smallest writes. */
40 private static final byte[][] _COMPACT
=
43 /** The longest length. */
44 private static final int _LONGEST
;
46 /** The classifier used. */
47 protected final SourceTargetClassifier classifier
;
51 // Calculate the sequence of bytes which result in the smallest for
53 byte[][] compact
= RatufaCoatBuiltInTaskAction
._COMPACT
;
54 for (int i
= 0; i
< 256; i
++)
58 for (String maybe
: Arrays
.<String
>asList(
60 "0" + Integer
.toOctalString(i
),
61 "0x" + Integer
.toHexString(i
)))
62 if (least
== null || maybe
.length() < least
.length())
65 // Use this one, but pre-encode it
66 compact
[i
] = least
.getBytes(StandardCharsets
.UTF_8
);
69 // Go through and calculate the longest sequence
70 int longest
= Integer
.MIN_VALUE
;
71 for (byte[] buf
: compact
)
72 longest
= Math
.max(longest
, buf
.length
);
77 * Initializes the task.
79 * @param __classifier The classifier used.
80 * @throws NullPointerException On null arguments.
83 public RatufaCoatBuiltInTaskAction(SourceTargetClassifier __classifier
)
84 throws NullPointerException
86 if (__classifier
== null)
87 throw new NullPointerException("NARG");
89 this.classifier
= __classifier
;
97 public void execute(Task __task
)
99 // Where is the ROM going?
100 Path output
= __task
.getOutputs().getFiles().getSingleFile().toPath();
102 // This could fail to write
103 Path tempFile
= null;
106 // Setup temporary file
107 tempFile
= Files
.createTempFile("builtin", ".c");
109 // Generate C code from this
110 try (PrintStream out
= new PrintStream(
111 Files
.newOutputStream(tempFile
,
112 StandardOpenOption
.WRITE
, StandardOpenOption
.CREATE
,
113 StandardOpenOption
.TRUNCATE_EXISTING
),
117 try (InputStream in
= RatufaCoatBuiltInTaskAction
.class
118 .getResourceAsStream("header.h"))
120 byte[] buf
= new byte[16384];
123 int rc
= in
.read(buf
);
129 out
.write(buf
, 0, rc
);
133 // Only when built-in is enabled
135 out
.println("#if defined(SQUIRRELJME_HAS_BUILTIN)");
138 // Which source set was this created for?
139 out
.print("const char* const sjme_builtInSourceSet = \"");
140 out
.print(this.classifier
.getSourceSet());
143 // Which VM Type was used?
144 out
.print("const char* const sjme_builtInVmType = \"");
145 out
.print(this.classifier
.getVmType()
146 .vmName(VMNameFormat
.PROPER_NOUN
));
149 // What is the "nice" name of the variant?
150 out
.print("const char* const sjme_builtInVariant = \"");
151 out
.print(this.classifier
.getBangletVariant().properNoun
);
154 // Which banglet was used?
155 out
.print("const char* const sjme_builtInBanglet = \"");
156 out
.print(this.classifier
.getBangletVariant().banglet
);
160 out
.println("const sjme_jubyte sjme_builtInRomData[] = {");
162 // Read in the entire ROM file since this is much faster
163 Path inputRom
= __task
.getInputs()
164 .getFiles().getSingleFile().toPath();
165 byte[] romData
= Files
.readAllBytes(inputRom
);
166 long romDate
= Files
.getLastModifiedTime(inputRom
).toMillis();
168 // Get ROM Unique Identifier (ROM Sum)
169 byte[] romDigest
= MessageDigest
.getInstance("SHA-256")
172 // Determine file size for progress metering
173 int romSize
= romData
.length
;
175 // Starting time for timing
176 long startNs
= System
.nanoTime();
178 // Output data buffer
180 int chunkyLimit
= 131072;
181 byte[] chunkyBuf
= new byte[chunkyLimit
+ 128];
184 byte[][] compact
= RatufaCoatBuiltInTaskAction
._COMPACT
;
185 for (int i
= 0, col
= 0, commaBit
= romSize
-1;
188 // This could take awhile!
189 if (chunkySize
> chunkyLimit
)
191 // This could be a slow process, so allow it to be
192 // stopped! Do not check all the time since this
194 if (Thread
.currentThread().isInterrupted())
195 throw new IOException("Conversion interrupted.");
197 // Dump the output chunk and reset it
198 out
.write(chunkyBuf
, 0, chunkySize
);
201 // Nanoseconds per progress point
202 long currentNanos
= (System
.nanoTime() -
204 long nanosPerIndex
= currentNanos
/ i
;
205 long estTotalNanos
= nanosPerIndex
* romSize
;
207 // Print progress dot
208 System
.err
.printf("Processed %d of %d bytes " +
209 "(%d seconds remaining)...%n",
211 (estTotalNanos
- currentNanos
) / 1_000_000_000L
);
215 // Write the digit down
216 for (byte b
: compact
[romData
[i
] & 0xFF])
217 chunkyBuf
[chunkySize
++] = b
;
219 // Do not add a final comma
221 chunkyBuf
[chunkySize
++] = ',';
223 // Put extra bytes on new line
224 if ((++col
) >= RatufaCoatBuiltInTaskAction
._COLS
)
226 chunkyBuf
[chunkySize
++] = '\n';
231 // Dump the chunks if there is anything left
233 out
.write(chunkyBuf
, 0, chunkySize
);
238 // Write ROM file size
239 out
.printf("const sjme_jint sjme_builtInRomSize = %d;",
244 out
.printf("const sjme_jbyte sjme_builtInRomId[] = " +
246 Arrays
.toString(romDigest
)
251 // Write ROM ID Length
252 out
.printf("const sjme_jint sjme_builtInRomIdLen = " +
258 out
.printf("const sjme_jint sjme_builtInRomDate[] = " +
259 "{SJME_JINT_C(%d), SJME_JINT_C(%d)};",
260 (int)(romDate
>>> 32), (int)romDate
);
263 // End Only when built-in is enabled
265 out
.println("#endif");
268 // Flush the output before closed
272 // It worked, so place it in the output
273 Files
.createDirectories(output
.getParent());
274 Files
.move(tempFile
, output
, StandardCopyOption
.REPLACE_EXISTING
);
277 // It did fail to write
278 catch (IOException
| NoSuchAlgorithmException e
)
280 throw new RuntimeException("Could not build ROM: " + output
, e
);
283 // Try to clear the temporary file if it exists still
286 if (tempFile
!= null)
289 Files
.delete(tempFile
);
291 catch (IOException ignored
)