8236129: Exe installers have wrong properties
Reviewed-by: herrick, almatvee
This commit is contained in:
parent
33d9178ebd
commit
88f3861cb7
src/jdk.incubator.jpackage
share/classes/jdk/incubator/jpackage/internal
windows
classes/jdk/incubator/jpackage/internal
native/libjpackage
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -137,6 +137,10 @@ class DottedVersion implements Comparable<String> {
|
||||
return value;
|
||||
}
|
||||
|
||||
int[] getComponents() {
|
||||
return components;
|
||||
}
|
||||
|
||||
final private int[] components;
|
||||
final private String value;
|
||||
final private boolean greedy;
|
||||
|
101
src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/OverridableResource.java
101
src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/OverridableResource.java
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -24,18 +24,28 @@
|
||||
*/
|
||||
package jdk.incubator.jpackage.internal;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import static jdk.incubator.jpackage.internal.StandardBundlerParam.RESOURCE_DIR;
|
||||
import jdk.incubator.jpackage.internal.resources.ResourceLocator;
|
||||
|
||||
|
||||
/**
|
||||
* Resource file that may have the default value supplied by jpackage. It can be
|
||||
* overridden by a file from resource directory set with {@code --resource-dir}
|
||||
@ -134,13 +144,39 @@ final class OverridableResource {
|
||||
return setExternal(toPath(v));
|
||||
}
|
||||
|
||||
Source saveToFile(Path dest) throws IOException {
|
||||
for (var source: sources) {
|
||||
if (source.getValue().apply(dest)) {
|
||||
return source.getKey();
|
||||
}
|
||||
Source saveToStream(OutputStream dest) throws IOException {
|
||||
if (dest == null) {
|
||||
return sendToConsumer(null);
|
||||
}
|
||||
return null;
|
||||
return sendToConsumer(new ResourceConsumer() {
|
||||
@Override
|
||||
public Path publicName() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(InputStream in) throws IOException {
|
||||
in.transferTo(dest);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Source saveToFile(Path dest) throws IOException {
|
||||
if (dest == null) {
|
||||
return sendToConsumer(null);
|
||||
}
|
||||
return sendToConsumer(new ResourceConsumer() {
|
||||
@Override
|
||||
public Path publicName() {
|
||||
return dest.getFileName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(InputStream in) throws IOException {
|
||||
Files.createDirectories(dest.getParent());
|
||||
Files.copy(in, dest, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Source saveToFile(File dest) throws IOException {
|
||||
@ -157,6 +193,15 @@ final class OverridableResource {
|
||||
RESOURCE_DIR.fetchFrom(params));
|
||||
}
|
||||
|
||||
private Source sendToConsumer(ResourceConsumer consumer) throws IOException {
|
||||
for (var source: sources) {
|
||||
if (source.getValue().apply(consumer)) {
|
||||
return source.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getPrintableCategory() {
|
||||
if (category != null) {
|
||||
return String.format("[%s]", category);
|
||||
@ -164,7 +209,7 @@ final class OverridableResource {
|
||||
return "";
|
||||
}
|
||||
|
||||
private boolean useExternal(Path dest) throws IOException {
|
||||
private boolean useExternal(ResourceConsumer dest) throws IOException {
|
||||
boolean used = externalPath != null && Files.exists(externalPath);
|
||||
if (used && dest != null) {
|
||||
Log.verbose(MessageFormat.format(I18N.getString(
|
||||
@ -179,7 +224,7 @@ final class OverridableResource {
|
||||
return used;
|
||||
}
|
||||
|
||||
private boolean useResourceDir(Path dest) throws IOException {
|
||||
private boolean useResourceDir(ResourceConsumer dest) throws IOException {
|
||||
boolean used = false;
|
||||
|
||||
if (dest == null && publicName == null) {
|
||||
@ -187,7 +232,7 @@ final class OverridableResource {
|
||||
}
|
||||
|
||||
final Path resourceName = Optional.ofNullable(publicName).orElseGet(
|
||||
() -> dest.getFileName());
|
||||
() -> dest.publicName());
|
||||
|
||||
if (resourceDir != null) {
|
||||
final Path customResource = resourceDir.resolve(resourceName);
|
||||
@ -213,14 +258,14 @@ final class OverridableResource {
|
||||
return used;
|
||||
}
|
||||
|
||||
private boolean useDefault(Path dest) throws IOException {
|
||||
private boolean useDefault(ResourceConsumer dest) throws IOException {
|
||||
boolean used = defaultName != null;
|
||||
if (used && dest != null) {
|
||||
final Path resourceName = Optional
|
||||
.ofNullable(logPublicName)
|
||||
.orElse(Optional
|
||||
.ofNullable(publicName)
|
||||
.orElseGet(() -> dest.getFileName()));
|
||||
.orElseGet(() -> dest.publicName()));
|
||||
Log.verbose(MessageFormat.format(
|
||||
I18N.getString("message.using-default-resource"),
|
||||
defaultName, getPrintableCategory(), resourceName));
|
||||
@ -232,7 +277,7 @@ final class OverridableResource {
|
||||
return used;
|
||||
}
|
||||
|
||||
private static List<String> substitute(Stream<String> lines,
|
||||
private static Stream<String> substitute(Stream<String> lines,
|
||||
Map<String, String> substitutionData) {
|
||||
return lines.map(line -> {
|
||||
String result = line;
|
||||
@ -241,7 +286,7 @@ final class OverridableResource {
|
||||
entry.getValue()).orElse(""));
|
||||
}
|
||||
return result;
|
||||
}).collect(Collectors.toList());
|
||||
});
|
||||
}
|
||||
|
||||
private static Path toPath(File v) {
|
||||
@ -251,17 +296,20 @@ final class OverridableResource {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void processResourceStream(InputStream rawResource, Path dest)
|
||||
throws IOException {
|
||||
private void processResourceStream(InputStream rawResource,
|
||||
ResourceConsumer dest) throws IOException {
|
||||
if (substitutionData == null) {
|
||||
Files.createDirectories(dest.getParent());
|
||||
Files.copy(rawResource, dest, StandardCopyOption.REPLACE_EXISTING);
|
||||
dest.consume(rawResource);
|
||||
} else {
|
||||
// Utf8 in and out
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(rawResource, StandardCharsets.UTF_8))) {
|
||||
Files.createDirectories(dest.getParent());
|
||||
Files.write(dest, substitute(reader.lines(), substitutionData));
|
||||
String data = substitute(reader.lines(), substitutionData).collect(
|
||||
Collectors.joining("\n"));
|
||||
try (InputStream in = new ByteArrayInputStream(data.getBytes(
|
||||
StandardCharsets.UTF_8))) {
|
||||
dest.consume(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -292,7 +340,12 @@ final class OverridableResource {
|
||||
private List<Map.Entry<Source, SourceHandler>> sources;
|
||||
|
||||
@FunctionalInterface
|
||||
static interface SourceHandler {
|
||||
public boolean apply(Path dest) throws IOException;
|
||||
private static interface SourceHandler {
|
||||
public boolean apply(ResourceConsumer dest) throws IOException;
|
||||
}
|
||||
|
||||
private static interface ResourceConsumer {
|
||||
public Path publicName();
|
||||
public void consume(InputStream in) throws IOException;
|
||||
}
|
||||
}
|
||||
|
240
src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/ExecutableRebrander.java
Normal file
240
src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/ExecutableRebrander.java
Normal file
@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.incubator.jpackage.internal;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.ResourceBundle;
|
||||
import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
|
||||
import static jdk.incubator.jpackage.internal.StandardBundlerParam.APP_NAME;
|
||||
import static jdk.incubator.jpackage.internal.StandardBundlerParam.COPYRIGHT;
|
||||
import static jdk.incubator.jpackage.internal.StandardBundlerParam.DESCRIPTION;
|
||||
import static jdk.incubator.jpackage.internal.StandardBundlerParam.ICON;
|
||||
import static jdk.incubator.jpackage.internal.StandardBundlerParam.TEMP_ROOT;
|
||||
import static jdk.incubator.jpackage.internal.StandardBundlerParam.VENDOR;
|
||||
import static jdk.incubator.jpackage.internal.StandardBundlerParam.VERSION;
|
||||
|
||||
|
||||
final class ExecutableRebrander {
|
||||
private static final ResourceBundle I18N = ResourceBundle.getBundle(
|
||||
"jdk.incubator.jpackage.internal.resources.WinResources");
|
||||
|
||||
private static final String LAUNCHER_PROPERTIES_TEMPLATE =
|
||||
"WinLauncher.template";
|
||||
|
||||
private static final String INSTALLER_PROPERTIES_TEMPLATE =
|
||||
"WinInstaller.template";
|
||||
|
||||
private static final String INSTALLER_PROPERTIES_RESOURE_DIR_ID =
|
||||
"WinInstaller.properties";
|
||||
|
||||
|
||||
void rebrandInstaller(Map<String, ? super Object> params, Path target)
|
||||
throws IOException {
|
||||
if (!target.isAbsolute()) {
|
||||
rebrandInstaller(params, target.toAbsolutePath());
|
||||
return;
|
||||
}
|
||||
rebrandExecutable(params, target, (resourceLock) -> {
|
||||
rebrandProperties(resourceLock, createResource(
|
||||
INSTALLER_PROPERTIES_TEMPLATE, params).setPublicName(
|
||||
INSTALLER_PROPERTIES_RESOURE_DIR_ID),
|
||||
createSubstituteData(params), target);
|
||||
});
|
||||
}
|
||||
|
||||
void rebrandLauncher(Map<String, ? super Object> params, Path icon,
|
||||
Path target) throws IOException {
|
||||
if (!target.isAbsolute() || (icon != null && !icon.isAbsolute())) {
|
||||
Path absIcon = null;
|
||||
if (icon != null) {
|
||||
absIcon = icon.toAbsolutePath();
|
||||
}
|
||||
rebrandLauncher(params, absIcon, target.toAbsolutePath());
|
||||
return;
|
||||
}
|
||||
rebrandExecutable(params, target, (resourceLock) -> {
|
||||
rebrandProperties(resourceLock, createResource(
|
||||
LAUNCHER_PROPERTIES_TEMPLATE, params).setPublicName(
|
||||
APP_NAME.fetchFrom(params) + ".properties"),
|
||||
createSubstituteData(params), target);
|
||||
|
||||
if (icon != null) {
|
||||
iconSwap(resourceLock, icon.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ExecutableRebrander addAction(UpdateResourceAction action) {
|
||||
if (extraActions == null) {
|
||||
extraActions = new ArrayList<>();
|
||||
}
|
||||
extraActions.add(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void rebrandExecutable(Map<String, ? super Object> params,
|
||||
Path target, UpdateResourceAction action) throws IOException {
|
||||
try {
|
||||
String tempDirectory = TEMP_ROOT.fetchFrom(params).getAbsolutePath();
|
||||
if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(
|
||||
tempDirectory)) {
|
||||
Log.verbose(MessageFormat.format(I18N.getString(
|
||||
"message.potential.windows.defender.issue"),
|
||||
tempDirectory));
|
||||
}
|
||||
|
||||
target.toFile().setWritable(true, true);
|
||||
|
||||
long resourceLock = lockResource(target.toString());
|
||||
if (resourceLock == 0) {
|
||||
throw new RuntimeException(MessageFormat.format(
|
||||
I18N.getString("error.lock-resource"), target));
|
||||
}
|
||||
|
||||
try {
|
||||
action.editResource(resourceLock);
|
||||
if (extraActions != null) {
|
||||
for (UpdateResourceAction extraAction: extraActions) {
|
||||
extraAction.editResource(resourceLock);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (resourceLock != 0) {
|
||||
unlockResource(resourceLock);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
target.toFile().setReadOnly();
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
static interface UpdateResourceAction {
|
||||
public void editResource(long resourceLock) throws IOException;
|
||||
}
|
||||
|
||||
private static String getFixedFileVersion(String value) {
|
||||
int[] versionComponents = DottedVersion.greedy(value).getComponents();
|
||||
int addComponentsCount = 4 - versionComponents.length;
|
||||
if (addComponentsCount > 0) {
|
||||
StringBuilder sb = new StringBuilder(value);
|
||||
do {
|
||||
sb.append('.');
|
||||
sb.append(0);
|
||||
} while (--addComponentsCount > 0);
|
||||
return sb.toString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private Map<String, String> createSubstituteData(
|
||||
Map<String, ? super Object> params) {
|
||||
Map<String, String> data = new HashMap<>();
|
||||
|
||||
String fixedFileVersion = getFixedFileVersion(VERSION.fetchFrom(params));
|
||||
|
||||
// mapping Java parameters in strings for version resource
|
||||
validateValueAndPut(data, "COMPANY_NAME", VENDOR, params);
|
||||
validateValueAndPut(data, "FILE_DESCRIPTION", DESCRIPTION, params);
|
||||
validateValueAndPut(data, "FILE_VERSION", VERSION, params);
|
||||
validateValueAndPut(data, "LEGAL_COPYRIGHT", COPYRIGHT, params);
|
||||
validateValueAndPut(data, "PRODUCT_NAME", APP_NAME, params);
|
||||
data.put("FIXEDFILEINFO_FILE_VERSION", fixedFileVersion);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private void rebrandProperties(long resourceLock, OverridableResource properties,
|
||||
Map<String, String> data, Path target) throws IOException {
|
||||
|
||||
String targetExecutableName = target.getFileName().toString();
|
||||
data.put("INTERNAL_NAME", targetExecutableName);
|
||||
data.put("ORIGINAL_FILENAME", targetExecutableName);
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
properties
|
||||
.setSubstitutionData(data)
|
||||
.setCategory(I18N.getString("resource.executable-properties-template"))
|
||||
.saveToStream(buffer);
|
||||
|
||||
final List<String> propList = new ArrayList<>();
|
||||
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(
|
||||
buffer.toByteArray()), StandardCharsets.UTF_8)) {
|
||||
final Properties configProp = new Properties();
|
||||
configProp.load(reader);
|
||||
configProp.forEach((k, v) -> {
|
||||
propList.add(k.toString());
|
||||
propList.add(v.toString());
|
||||
});
|
||||
}
|
||||
|
||||
if (versionSwap(resourceLock, propList.toArray(String[]::new)) != 0) {
|
||||
throw new RuntimeException(MessageFormat.format(
|
||||
I18N.getString("error.version-swap"), target));
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateValueAndPut(
|
||||
Map<String, String> data, String key,
|
||||
BundlerParamInfo<String> param,
|
||||
Map<String, ? super Object> params) {
|
||||
String value = param.fetchFrom(params);
|
||||
if (value.contains("\r") || value.contains("\n")) {
|
||||
Log.error("Configuration Parameter " + param.getID()
|
||||
+ " contains multiple lines of text, ignore it");
|
||||
data.put(key, "");
|
||||
return;
|
||||
}
|
||||
data.put(key, value);
|
||||
}
|
||||
|
||||
private List<UpdateResourceAction> extraActions;
|
||||
|
||||
static {
|
||||
System.loadLibrary("jpackage");
|
||||
}
|
||||
|
||||
private static native long lockResource(String executable);
|
||||
|
||||
private static native void unlockResource(long resourceLock);
|
||||
|
||||
private static native int iconSwap(long resourceLock, String iconTarget);
|
||||
|
||||
private static native int versionSwap(long resourceLock, String[] executableProperties);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -64,7 +64,7 @@ public class WinExeBundler extends AbstractBundler {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getString("exe.bundler.name");
|
||||
return I18N.getString("exe.bundler.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -117,48 +117,45 @@ public class WinExeBundler extends AbstractBundler {
|
||||
.setEnvironmentVariable("JpMsiFile", msi.getAbsolutePath().toString())
|
||||
.run(params);
|
||||
|
||||
return buildEXE(msi, outdir);
|
||||
return buildEXE(params, msi, outdir);
|
||||
} catch (IOException ex) {
|
||||
Log.verbose(ex);
|
||||
throw new PackagerException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private File buildEXE(File msi, File outdir)
|
||||
throws IOException {
|
||||
private File buildEXE(Map<String, ? super Object> params, File msi,
|
||||
File outdir) throws IOException {
|
||||
|
||||
Log.verbose(MessageFormat.format(
|
||||
getString("message.outputting-to-location"),
|
||||
I18N.getString("message.outputting-to-location"),
|
||||
outdir.getAbsolutePath()));
|
||||
|
||||
// Copy template msi wrapper next to msi file
|
||||
String exePath = msi.getAbsolutePath();
|
||||
exePath = exePath.substring(0, exePath.lastIndexOf('.')) + ".exe";
|
||||
final Path exePath = IOUtils.replaceSuffix(msi.toPath(), ".exe");
|
||||
try (InputStream is = OverridableResource.readDefault(EXE_WRAPPER_NAME)) {
|
||||
Files.copy(is, Path.of(exePath));
|
||||
Files.copy(is, exePath);
|
||||
}
|
||||
// Embed msi in msi wrapper exe.
|
||||
embedMSI(exePath, msi.getAbsolutePath());
|
||||
|
||||
new ExecutableRebrander().addAction((resourceLock) -> {
|
||||
// Embed msi in msi wrapper exe.
|
||||
embedMSI(resourceLock, msi.getAbsolutePath());
|
||||
}).rebrandInstaller(params, exePath);
|
||||
|
||||
Path dstExePath = Paths.get(outdir.getAbsolutePath(),
|
||||
Path.of(exePath).getFileName().toString());
|
||||
exePath.getFileName().toString());
|
||||
Files.deleteIfExists(dstExePath);
|
||||
|
||||
Files.copy(Path.of(exePath), dstExePath);
|
||||
Files.copy(exePath, dstExePath);
|
||||
|
||||
Log.verbose(MessageFormat.format(
|
||||
getString("message.output-location"),
|
||||
I18N.getString("message.output-location"),
|
||||
outdir.getAbsolutePath()));
|
||||
|
||||
return dstExePath.toFile();
|
||||
}
|
||||
|
||||
private static String getString(String key)
|
||||
throws MissingResourceException {
|
||||
return I18N.getString(key);
|
||||
}
|
||||
|
||||
private final WinMsiBundler msiBundler = new WinMsiBundler();
|
||||
|
||||
private static native int embedMSI(String exePath, String msiPath);
|
||||
private static native int embedMSI(long resourceLock, String msiPath);
|
||||
}
|
||||
|
@ -26,49 +26,28 @@
|
||||
package jdk.incubator.jpackage.internal;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.Writer;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
|
||||
|
||||
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
|
||||
|
||||
public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
|
||||
|
||||
static {
|
||||
System.loadLibrary("jpackage");
|
||||
}
|
||||
|
||||
private static final ResourceBundle I18N = ResourceBundle.getBundle(
|
||||
"jdk.incubator.jpackage.internal.resources.WinResources");
|
||||
|
||||
private final static String LIBRARY_NAME = "applauncher.dll";
|
||||
|
||||
private final static String TEMPLATE_APP_ICON ="java48.ico";
|
||||
|
||||
private static final String EXECUTABLE_PROPERTIES_TEMPLATE =
|
||||
"WinLauncher.template";
|
||||
private static final String TEMPLATE_APP_ICON ="java48.ico";
|
||||
|
||||
private final Path root;
|
||||
private final Path appDir;
|
||||
@ -77,13 +56,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
|
||||
private final Path mdir;
|
||||
private final Path binDir;
|
||||
|
||||
public static final BundlerParamInfo<Boolean> REBRAND_EXECUTABLE =
|
||||
new WindowsBundlerParam<>(
|
||||
"win.launcher.rebrand",
|
||||
Boolean.class,
|
||||
params -> Boolean.TRUE,
|
||||
(s, p) -> Boolean.valueOf(s));
|
||||
|
||||
public static final BundlerParamInfo<File> ICON_ICO =
|
||||
new StandardBundlerParam<>(
|
||||
"icon.ico",
|
||||
@ -150,16 +122,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
|
||||
return "app/" + APP_NAME.fetchFrom(params) +".cfg";
|
||||
}
|
||||
|
||||
private File getConfig_ExecutableProperties(
|
||||
Map<String, ? super Object> params) {
|
||||
return new File(getConfigRoot(params),
|
||||
APP_NAME.fetchFrom(params) + ".properties");
|
||||
}
|
||||
|
||||
File getConfigRoot(Map<String, ? super Object> params) {
|
||||
return CONFIG_ROOT.fetchFrom(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getAppDir() {
|
||||
return appDir;
|
||||
@ -200,41 +162,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
|
||||
public void prepareJreFiles(Map<String, ? super Object> params)
|
||||
throws IOException {}
|
||||
|
||||
private void validateValueAndPut(
|
||||
Map<String, String> data, String key,
|
||||
BundlerParamInfo<String> param,
|
||||
Map<String, ? super Object> params) {
|
||||
String value = param.fetchFrom(params);
|
||||
if (value.contains("\r") || value.contains("\n")) {
|
||||
Log.error("Configuration Parameter " + param.getID()
|
||||
+ " contains multiple lines of text, ignore it");
|
||||
data.put(key, "");
|
||||
return;
|
||||
}
|
||||
data.put(key, value);
|
||||
}
|
||||
|
||||
protected void prepareExecutableProperties(
|
||||
Map<String, ? super Object> params) throws IOException {
|
||||
|
||||
Map<String, String> data = new HashMap<>();
|
||||
|
||||
// mapping Java parameters in strings for version resource
|
||||
validateValueAndPut(data, "COMPANY_NAME", VENDOR, params);
|
||||
validateValueAndPut(data, "FILE_DESCRIPTION", DESCRIPTION, params);
|
||||
validateValueAndPut(data, "FILE_VERSION", VERSION, params);
|
||||
data.put("INTERNAL_NAME", getLauncherName(params));
|
||||
validateValueAndPut(data, "LEGAL_COPYRIGHT", COPYRIGHT, params);
|
||||
data.put("ORIGINAL_FILENAME", getLauncherName(params));
|
||||
validateValueAndPut(data, "PRODUCT_NAME", APP_NAME, params);
|
||||
validateValueAndPut(data, "PRODUCT_VERSION", VERSION, params);
|
||||
|
||||
createResource(EXECUTABLE_PROPERTIES_TEMPLATE, params)
|
||||
.setCategory(I18N.getString("resource.executable-properties-template"))
|
||||
.setSubstitutionData(data)
|
||||
.saveToFile(getConfig_ExecutableProperties(params));
|
||||
}
|
||||
|
||||
private void createLauncherForEntryPoint(Map<String, ? super Object> params,
|
||||
Map<String, ? super Object> mainParams) throws IOException {
|
||||
|
||||
@ -251,8 +178,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
|
||||
writeCfgFile(params, root.resolve(
|
||||
getLauncherCfgName(params)).toFile());
|
||||
|
||||
prepareExecutableProperties(params);
|
||||
|
||||
// Copy executable to bin folder
|
||||
Path executableFile = binDir.resolve(getLauncherName(params));
|
||||
|
||||
@ -261,47 +186,11 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
|
||||
writeEntry(is_launcher, executableFile);
|
||||
}
|
||||
|
||||
File launcher = executableFile.toFile();
|
||||
launcher.setWritable(true, true);
|
||||
// Update branding of launcher executable
|
||||
new ExecutableRebrander().rebrandLauncher(params, iconTarget, executableFile);
|
||||
|
||||
// Update branding of EXE file
|
||||
if (REBRAND_EXECUTABLE.fetchFrom(params)) {
|
||||
try {
|
||||
String tempDirectory = WindowsDefender.getUserTempDirectory();
|
||||
if (Arguments.CLIOptions.context().userProvidedBuildRoot) {
|
||||
tempDirectory =
|
||||
TEMP_ROOT.fetchFrom(params).getAbsolutePath();
|
||||
}
|
||||
if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(
|
||||
tempDirectory)) {
|
||||
Log.verbose(MessageFormat.format(I18N.getString(
|
||||
"message.potential.windows.defender.issue"),
|
||||
tempDirectory));
|
||||
}
|
||||
|
||||
launcher.setWritable(true);
|
||||
|
||||
if (iconTarget != null) {
|
||||
iconSwap(iconTarget.toAbsolutePath().toString(),
|
||||
launcher.getAbsolutePath());
|
||||
}
|
||||
|
||||
File executableProperties =
|
||||
getConfig_ExecutableProperties(params);
|
||||
|
||||
if (executableProperties.exists()) {
|
||||
if (versionSwap(executableProperties.getAbsolutePath(),
|
||||
launcher.getAbsolutePath()) != 0) {
|
||||
throw new RuntimeException(MessageFormat.format(
|
||||
I18N.getString("error.version-swap"),
|
||||
executableProperties.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
executableFile.toFile().setExecutable(true);
|
||||
executableFile.toFile().setReadOnly();
|
||||
}
|
||||
}
|
||||
executableFile.toFile().setExecutable(true);
|
||||
executableFile.toFile().setReadOnly();
|
||||
}
|
||||
|
||||
private void copyApplication(Map<String, ? super Object> params)
|
||||
@ -321,10 +210,4 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static native int iconSwap(String iconTarget, String launcher);
|
||||
|
||||
private static native int versionSwap(String executableProperties,
|
||||
String launcher);
|
||||
|
||||
}
|
||||
|
10
src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinInstaller.template
Normal file
10
src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinInstaller.template
Normal file
@ -0,0 +1,10 @@
|
||||
FIXEDFILEINFO_FileVersion=FIXEDFILEINFO_FILE_VERSION
|
||||
FIXEDFILEINFO_ProductVersion=FIXEDFILEINFO_FILE_VERSION
|
||||
CompanyName=COMPANY_NAME
|
||||
FileDescription=Installer of FILE_DESCRIPTION
|
||||
FileVersion=FILE_VERSION
|
||||
InternalName=INTERNAL_NAME
|
||||
LegalCopyright=LEGAL_COPYRIGHT
|
||||
OriginalFilename=ORIGINAL_FILENAME
|
||||
ProductName=PRODUCT_NAME Installer
|
||||
ProductVersion=FILE_VERSION
|
@ -1,3 +1,5 @@
|
||||
FIXEDFILEINFO_FileVersion=FIXEDFILEINFO_FILE_VERSION
|
||||
FIXEDFILEINFO_ProductVersion=FIXEDFILEINFO_FILE_VERSION
|
||||
CompanyName=COMPANY_NAME
|
||||
FileDescription=FILE_DESCRIPTION
|
||||
FileVersion=FILE_VERSION
|
||||
@ -5,4 +7,4 @@ InternalName=INTERNAL_NAME
|
||||
LegalCopyright=LEGAL_COPYRIGHT
|
||||
OriginalFilename=ORIGINAL_FILENAME
|
||||
ProductName=PRODUCT_NAME
|
||||
ProductVersion=PRODUCT_VERSION
|
||||
ProductVersion=FILE_VERSION
|
||||
|
@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include "ByteBuffer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
ByteBuffer::ByteBuffer() {
|
||||
buffer.reserve(1024);
|
||||
}
|
||||
|
||||
ByteBuffer::~ByteBuffer() {
|
||||
}
|
||||
|
||||
LPBYTE ByteBuffer::getPtr() {
|
||||
return &buffer[0];
|
||||
}
|
||||
|
||||
size_t ByteBuffer::getPos() {
|
||||
return buffer.size();
|
||||
}
|
||||
|
||||
void ByteBuffer::AppendString(wstring str) {
|
||||
size_t len = (str.size() + 1) * sizeof (WCHAR);
|
||||
AppendBytes((BYTE*) str.c_str(), len);
|
||||
}
|
||||
|
||||
void ByteBuffer::AppendWORD(WORD word) {
|
||||
AppendBytes((BYTE*) & word, sizeof (WORD));
|
||||
}
|
||||
|
||||
void ByteBuffer::Align(size_t bytesNumber) {
|
||||
size_t pos = getPos();
|
||||
if (pos % bytesNumber) {
|
||||
DWORD dwNull = 0;
|
||||
size_t len = bytesNumber - pos % bytesNumber;
|
||||
AppendBytes((BYTE*) & dwNull, len);
|
||||
}
|
||||
}
|
||||
|
||||
void ByteBuffer::AppendBytes(BYTE* ptr, size_t len) {
|
||||
buffer.insert(buffer.end(), ptr, ptr + len);
|
||||
}
|
||||
|
||||
void ByteBuffer::ReplaceWORD(size_t offset, WORD word) {
|
||||
ReplaceBytes(offset, (BYTE*) & word, sizeof (WORD));
|
||||
}
|
||||
|
||||
void ByteBuffer::ReplaceBytes(size_t offset, BYTE* ptr, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
buffer[offset + i] = *(ptr + i);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -96,7 +96,7 @@ void PrintError() {
|
||||
// Note: We do not check here that iconTarget is valid icon.
|
||||
// Java code will already do this for us.
|
||||
|
||||
bool ChangeIcon(wstring iconTarget, wstring launcher) {
|
||||
bool ChangeIcon(HANDLE update, const wstring& iconTarget) {
|
||||
WORD language = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT);
|
||||
|
||||
HANDLE icon = CreateFile(iconTarget.c_str(), GENERIC_READ, 0, NULL,
|
||||
@ -154,15 +154,6 @@ bool ChangeIcon(wstring iconTarget, wstring launcher) {
|
||||
}
|
||||
|
||||
// Store images in .EXE
|
||||
HANDLE update = BeginUpdateResource(launcher.c_str(), FALSE);
|
||||
if (update == NULL) {
|
||||
free(lpid);
|
||||
free(lpgid);
|
||||
CloseHandle(icon);
|
||||
PrintError();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < lpid->idCount; i++) {
|
||||
LPBYTE lpBuffer = (LPBYTE) malloc(lpid->idEntries[i].dwBytesInRes);
|
||||
SetFilePointer(icon, lpid->idEntries[i].dwImageOffset,
|
||||
@ -195,10 +186,5 @@ bool ChangeIcon(wstring iconTarget, wstring launcher) {
|
||||
|
||||
free(lpgid);
|
||||
|
||||
if (EndUpdateResource(update, FALSE) == FALSE) {
|
||||
PrintError();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -28,9 +28,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool ChangeIcon(wstring iconTarget, wstring launcher);
|
||||
bool ChangeIcon(HANDLE update, const std::wstring& iconTarget);
|
||||
|
||||
#endif // ICONSWAP_H
|
||||
|
||||
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include "JniUtils.h"
|
||||
#include "ErrorHandling.h"
|
||||
#include "Toolbox.h"
|
||||
|
||||
|
||||
namespace jni {
|
||||
|
||||
void JniObjWithEnv::LocalRefDeleter::operator()(pointer v) {
|
||||
if (v.env && v.obj) {
|
||||
v.env->DeleteLocalRef(v.obj);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef TSTRINGS_WITH_WCHAR
|
||||
std::wstring toUnicodeString(JNIEnv *env, jstring val) {
|
||||
const jchar* chars = env->GetStringChars(val, 0);
|
||||
if (!chars) {
|
||||
JP_THROW("GetStringChars() failed");
|
||||
}
|
||||
|
||||
const auto releaseStringChars =
|
||||
runAtEndOfScope([env, val, chars]() -> void {
|
||||
env->ReleaseStringChars(val, chars);
|
||||
});
|
||||
|
||||
const jsize len = env->GetStringLength(val);
|
||||
|
||||
return std::wstring(reinterpret_cast<const wchar_t*>(chars), len);
|
||||
}
|
||||
|
||||
|
||||
jstring toJString(JNIEnv *env, const std::wstring& val) {
|
||||
jstring result = env->NewString(
|
||||
reinterpret_cast<const jchar*>(val.c_str()), jsize(val.size()));
|
||||
if (!result) {
|
||||
JP_THROW("NewString() failed");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
tstring_array toUnicodeStringArray(JNIEnv *env, jobjectArray val) {
|
||||
tstring_array result;
|
||||
|
||||
const jsize len = env->GetArrayLength(val);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
LocalRef localRef(JniObjWithEnv(env,
|
||||
env->GetObjectArrayElement(val, i)));
|
||||
result.push_back(toUnicodeString(env,
|
||||
static_cast<jstring>(localRef.get().obj)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace jni
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,31 +23,49 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#ifndef VERSIONINFOSWAP_H
|
||||
#define VERSIONINFOSWAP_H
|
||||
#ifndef JNIUTILS_H
|
||||
#define JNIUTILS_H
|
||||
|
||||
#include "ByteBuffer.h"
|
||||
#include <map>
|
||||
#include "jni.h"
|
||||
#include "tstrings.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class VersionInfoSwap {
|
||||
public:
|
||||
VersionInfoSwap(wstring executableProperties, wstring launcher);
|
||||
namespace jni {
|
||||
|
||||
bool PatchExecutable();
|
||||
struct JniObjWithEnv {
|
||||
JniObjWithEnv(): env(0), obj(0) {
|
||||
|
||||
private:
|
||||
wstring m_executableProperties;
|
||||
wstring m_launcher;
|
||||
}
|
||||
|
||||
map<wstring, wstring> m_props;
|
||||
JniObjWithEnv(JNIEnv *env, jobject obj) : env(env), obj(obj) {
|
||||
}
|
||||
|
||||
bool LoadFromPropertyFile();
|
||||
bool CreateNewResource(ByteBuffer *buf);
|
||||
bool UpdateResource(LPVOID lpResLock, DWORD size);
|
||||
bool FillFixedFileInfo(VS_FIXEDFILEINFO *fxi);
|
||||
bool operator == (const JniObjWithEnv& other) const {
|
||||
return env == other.env && obj == other.obj;
|
||||
}
|
||||
|
||||
bool operator != (const JniObjWithEnv& other) const {
|
||||
return ! operator == (other);
|
||||
}
|
||||
|
||||
JNIEnv *env;
|
||||
jobject obj;
|
||||
|
||||
struct LocalRefDeleter {
|
||||
typedef JniObjWithEnv pointer;
|
||||
|
||||
void operator()(pointer v);
|
||||
};
|
||||
};
|
||||
|
||||
#endif // VERSIONINFOSWAP_H
|
||||
typedef std::unique_ptr<JniObjWithEnv, JniObjWithEnv::LocalRefDeleter> LocalRef;
|
||||
|
||||
tstring toUnicodeString(JNIEnv *env, jstring val);
|
||||
|
||||
jstring toJString(JNIEnv *env, const tstring& val);
|
||||
|
||||
tstring_array toUnicodeStringArray(JNIEnv *env, jobjectArray val);
|
||||
|
||||
} // namespace jni
|
||||
|
||||
#endif // JNIUTILS_H
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -37,12 +37,19 @@ ResourceEditor::FileLock::FileLock(const std::wstring& binaryPath) {
|
||||
<< binaryPath << ") failed", BeginUpdateResource));
|
||||
}
|
||||
|
||||
ownHandle(true);
|
||||
discard(false);
|
||||
}
|
||||
|
||||
|
||||
ResourceEditor::FileLock::FileLock(HANDLE h): h(h) {
|
||||
ownHandle(false);
|
||||
discard(false);
|
||||
}
|
||||
|
||||
|
||||
ResourceEditor::FileLock::~FileLock() {
|
||||
if (!EndUpdateResource(h, theDiscard)) {
|
||||
if (theOwnHandle && !EndUpdateResource(h, theDiscard)) {
|
||||
JP_NO_THROW(JP_THROW(SysError(tstrings::any()
|
||||
<< "EndUpdateResource(" << h << ") failed.", EndUpdateResource)));
|
||||
}
|
||||
@ -85,8 +92,8 @@ ResourceEditor& ResourceEditor::id(LPCWSTR v) {
|
||||
theId = printer.str();
|
||||
} else {
|
||||
theId = v;
|
||||
theIdPtr = theId.c_str();
|
||||
}
|
||||
theIdPtr = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -35,7 +35,8 @@ class ResourceEditor {
|
||||
public:
|
||||
class FileLock {
|
||||
public:
|
||||
FileLock(const std::wstring& binaryPath);
|
||||
explicit FileLock(const std::wstring& binaryPath);
|
||||
explicit FileLock(HANDLE h);
|
||||
~FileLock();
|
||||
|
||||
HANDLE get() const {
|
||||
@ -46,11 +47,17 @@ public:
|
||||
theDiscard = v;
|
||||
}
|
||||
|
||||
FileLock& ownHandle(bool v) {
|
||||
theOwnHandle = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
FileLock(const FileLock&);
|
||||
FileLock& operator=(const FileLock&);
|
||||
private:
|
||||
HANDLE h;
|
||||
bool theOwnHandle;
|
||||
bool theDiscard;
|
||||
};
|
||||
|
||||
|
@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include "Windows.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#define BUFFER_SIZE 4096
|
||||
|
||||
wstring GetStringFromJString(JNIEnv *pEnv, jstring jstr) {
|
||||
const jchar *pJChars = pEnv->GetStringChars(jstr, NULL);
|
||||
if (pJChars == NULL) {
|
||||
return wstring(L"");
|
||||
}
|
||||
|
||||
wstring wstr(pJChars);
|
||||
|
||||
pEnv->ReleaseStringChars(jstr, pJChars);
|
||||
|
||||
return wstr;
|
||||
}
|
||||
|
||||
jstring GetJStringFromString(JNIEnv *pEnv,
|
||||
const jchar *unicodeChars, jsize len) {
|
||||
return pEnv->NewString(unicodeChars, len);
|
||||
}
|
||||
|
||||
wstring GetLongPath(wstring path) {
|
||||
wstring result(L"");
|
||||
|
||||
size_t len = path.length();
|
||||
if (len > 1) {
|
||||
if (path.at(len - 1) == '\\') {
|
||||
path.erase(len - 1);
|
||||
}
|
||||
}
|
||||
|
||||
TCHAR *pBuffer = new TCHAR[BUFFER_SIZE];
|
||||
if (pBuffer != NULL) {
|
||||
DWORD dwResult = GetLongPathName(path.c_str(), pBuffer, BUFFER_SIZE);
|
||||
if (dwResult > 0 && dwResult < BUFFER_SIZE) {
|
||||
result = wstring(pBuffer);
|
||||
} else {
|
||||
delete [] pBuffer;
|
||||
pBuffer = new TCHAR[dwResult];
|
||||
if (pBuffer != NULL) {
|
||||
DWORD dwResult2 =
|
||||
GetLongPathName(path.c_str(), pBuffer, dwResult);
|
||||
if (dwResult2 == (dwResult - 1)) {
|
||||
result = wstring(pBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pBuffer != NULL) {
|
||||
delete [] pBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <string>
|
||||
#include "jni.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
wstring GetStringFromJString(JNIEnv *pEnv, jstring jstr);
|
||||
jstring GetJStringFromString(JNIEnv *pEnv, const jchar *unicodeChars,
|
||||
jsize len);
|
||||
|
||||
wstring GetLongPath(wstring path);
|
||||
|
||||
#endif // UTILS_H
|
@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include "VersionInfo.h"
|
||||
#include "ResourceEditor.h"
|
||||
#include "ErrorHandling.h"
|
||||
#include "Toolbox.h"
|
||||
|
||||
|
||||
VersionInfo::VersionInfo() {
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class FixedFileVersion {
|
||||
public:
|
||||
FixedFileVersion(const std::wstring& value) {
|
||||
if (4 != swscanf_s(value.c_str(), L"%d.%d.%d.%d", components + 0,
|
||||
components + 1, components + 2, components + 3)) {
|
||||
JP_THROW(tstrings::any()
|
||||
<< "Malformed file version value: ["
|
||||
<< value
|
||||
<< "]");
|
||||
forEach(components, [&value](int component) -> void {
|
||||
if (USHRT_MAX < component) {
|
||||
JP_THROW(tstrings::any()
|
||||
<< "Invalid file version value: ["
|
||||
<< value
|
||||
<< "]");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void apply(DWORD& ms, DWORD& ls) const {
|
||||
ms = MAKELONG(components[1], components[0]);
|
||||
ls = MAKELONG(components[3], components[2]);
|
||||
}
|
||||
|
||||
private:
|
||||
int components[4];
|
||||
};
|
||||
|
||||
|
||||
std::ostream& writeWORD(std::ostream& cout, size_t v) {
|
||||
if (USHRT_MAX < v) {
|
||||
JP_THROW("Invalid WORD value");
|
||||
}
|
||||
return cout.write(reinterpret_cast<const char*>(&v), sizeof(WORD));
|
||||
}
|
||||
|
||||
|
||||
std::ostream& writeDWORD(std::ostream& cout, size_t v) {
|
||||
if (UINT_MAX < v) {
|
||||
JP_THROW("Invalid DWORD value");
|
||||
}
|
||||
|
||||
return cout.write(reinterpret_cast<const char*>(&v), sizeof(DWORD));
|
||||
}
|
||||
|
||||
|
||||
std::ostream& write(std::ostream& cout, const VS_FIXEDFILEINFO& v) {
|
||||
return cout.write(reinterpret_cast<const char*>(&v), sizeof(v));
|
||||
}
|
||||
|
||||
std::ostream& write(std::ostream& cout, const std::wstring& s) {
|
||||
return cout.write(reinterpret_cast<const char*>(s.c_str()),
|
||||
(s.size() + 1 /* trailing 0 */) * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
void add32bitPadding(std::ostream& cout) {
|
||||
enum { WordAlign = 2 };
|
||||
const std::streampos pos = cout.tellp();
|
||||
if (pos % 2) {
|
||||
JP_THROW("Invalid data written in the stream");
|
||||
}
|
||||
const int padding = WordAlign - (pos / 2) % WordAlign;
|
||||
if (WordAlign != padding) {
|
||||
for (int i = 0; i < padding; ++i) {
|
||||
writeWORD(cout, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class StreamSize {
|
||||
public:
|
||||
StreamSize(std::ostream& out): stream(out), anchor(out.tellp()) {
|
||||
writeWORD(stream, 0); // placeholder
|
||||
}
|
||||
|
||||
~StreamSize() {
|
||||
JP_TRY;
|
||||
|
||||
const std::streampos curPos = stream.tellp();
|
||||
const std::streampos size = curPos - anchor;
|
||||
stream.seekp(anchor);
|
||||
writeWORD(stream, size);
|
||||
stream.seekp(curPos);
|
||||
|
||||
JP_CATCH_ALL;
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream& stream;
|
||||
std::streampos anchor;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
VersionInfo& VersionInfo::setProperty(
|
||||
const std::wstring& id, const std::wstring& value) {
|
||||
props[id] = value;
|
||||
|
||||
if (id == L"FIXEDFILEINFO_FileVersion") {
|
||||
// Validate input
|
||||
const ::FixedFileVersion validate(value);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
const VersionInfo& VersionInfo::apply(
|
||||
const ResourceEditor::FileLock& fileLock) const {
|
||||
if (props.find(L"FIXEDFILEINFO_FileVersion") == props.end()) {
|
||||
JP_THROW("Missing mandatory FILEVERSION property");
|
||||
}
|
||||
|
||||
std::stringstream buf(
|
||||
std::stringstream::in | std::stringstream::out | std::stringstream::binary);
|
||||
buf.exceptions(std::ios::failbit | std::ios::badbit);
|
||||
|
||||
fillBuffer(buf);
|
||||
|
||||
buf.seekg(0);
|
||||
|
||||
ResourceEditor()
|
||||
.id(MAKEINTRESOURCE(VS_VERSION_INFO))
|
||||
.type(RT_VERSION)
|
||||
.apply(fileLock, buf);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
void VersionInfo::fillBuffer(std::ostream& buf) const {
|
||||
// Fill VS_VERSIONINFO pseudo structure
|
||||
StreamSize versionInfoLength(buf); // wLength
|
||||
writeWORD(buf, sizeof(VS_FIXEDFILEINFO)); // wValueLength
|
||||
writeWORD(buf, 0); // wType
|
||||
write(buf, L"VS_VERSION_INFO"); // szKey
|
||||
add32bitPadding(buf); // Padding1
|
||||
write(buf, createFIXEDFILEINFO()); // Value
|
||||
add32bitPadding(buf); // Padding2
|
||||
|
||||
const DWORD neutralLangId = (0x04b0 | MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) << 16);
|
||||
const DWORD engLangId = (0x04b0 | MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US) << 16);
|
||||
|
||||
do {
|
||||
// Fill StringFileInfo pseudo structure
|
||||
StreamSize stringFileInfoLength(buf); // wLength
|
||||
writeWORD(buf, 0); // wValueLength
|
||||
writeWORD(buf, 1); // wType
|
||||
write(buf, L"StringFileInfo"); // szKey
|
||||
add32bitPadding(buf); // Padding
|
||||
|
||||
// Fill StringTable pseudo structure
|
||||
StreamSize stringTableLength(buf); // wLength
|
||||
writeWORD(buf, 0); // wValueLength
|
||||
writeWORD(buf, 1); // wType
|
||||
|
||||
const std::wstring strLangId = (std::wstringstream()
|
||||
<< std::uppercase
|
||||
<< std::hex
|
||||
<< std::setw(8)
|
||||
<< std::setfill(L'0')
|
||||
<< engLangId).str();
|
||||
write(buf, strLangId); // szKey
|
||||
add32bitPadding(buf); // Padding
|
||||
|
||||
forEach(props, [&buf](const PropertyMap::value_type& entry) -> void {
|
||||
if (entry.first.rfind(L"FIXEDFILEINFO_", 0) == 0) {
|
||||
// Ignore properties to be used to initialize data in
|
||||
// VS_FIXEDFILEINFO structure.
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill String pseudo structure
|
||||
StreamSize stringLength(buf); // wLength
|
||||
writeWORD(buf, entry.second.size()); // wValueLength
|
||||
writeWORD(buf, 1); // wType
|
||||
write(buf, entry.first); // wKey
|
||||
add32bitPadding(buf); // Padding1
|
||||
write(buf, entry.second); // Value
|
||||
add32bitPadding(buf); // Padding2
|
||||
});
|
||||
} while (0);
|
||||
|
||||
// Fill VarFileInfo pseudo structure
|
||||
StreamSize varFileInfoLength(buf); // wLength
|
||||
writeWORD(buf, 0); // wValueLength
|
||||
writeWORD(buf, 1); // wType
|
||||
write(buf, L"VarFileInfo"); // szKey
|
||||
add32bitPadding(buf); // Padding
|
||||
|
||||
// Fill Var pseudo structure
|
||||
StreamSize varLength(buf); // wLength
|
||||
writeWORD(buf, sizeof(DWORD)); // wValueLength
|
||||
writeWORD(buf, 0); // wType
|
||||
write(buf, L"Translation"); // szKey
|
||||
add32bitPadding(buf); // Padding
|
||||
writeDWORD(buf, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)); // Value
|
||||
}
|
||||
|
||||
|
||||
VS_FIXEDFILEINFO VersionInfo::createFIXEDFILEINFO() const {
|
||||
const ::FixedFileVersion fileVersion(props.find(
|
||||
L"FIXEDFILEINFO_FileVersion")->second);
|
||||
|
||||
VS_FIXEDFILEINFO result;
|
||||
ZeroMemory(&result, sizeof(result));
|
||||
|
||||
result.dwSignature = 0xFEEF04BD;
|
||||
result.dwStrucVersion = 0x00010000;
|
||||
result.dwFileOS = VOS_NT_WINDOWS32;
|
||||
result.dwFileType = VFT_APP;
|
||||
|
||||
fileVersion.apply(result.dwFileVersionMS, result.dwFileVersionLS);
|
||||
|
||||
PropertyMap::const_iterator entry = props.find(
|
||||
L"FIXEDFILEINFO_ProductVersion");
|
||||
if (entry == props.end()) {
|
||||
fileVersion.apply(result.dwProductVersionMS, result.dwProductVersionLS);
|
||||
} else {
|
||||
bool fatalError = false;
|
||||
try {
|
||||
const ::FixedFileVersion productVersion(entry->second);
|
||||
fatalError = true;
|
||||
productVersion.apply(result.dwProductVersionMS,
|
||||
result.dwProductVersionLS);
|
||||
} catch (const std::exception&) {
|
||||
if (fatalError) {
|
||||
throw;
|
||||
}
|
||||
// Failed to parse product version as four component version string.
|
||||
fileVersion.apply(result.dwProductVersionMS,
|
||||
result.dwProductVersionLS);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,35 +23,40 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#ifndef BYTEBUFFER_H
|
||||
#define BYTEBUFFER_H
|
||||
#ifndef VERSIONINFO_H
|
||||
#define VERSIONINFO_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "ResourceEditor.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class ByteBuffer {
|
||||
class VersionInfo {
|
||||
public:
|
||||
ByteBuffer();
|
||||
~ByteBuffer();
|
||||
VersionInfo();
|
||||
|
||||
LPBYTE getPtr();
|
||||
size_t getPos();
|
||||
VersionInfo& setProperty(const std::wstring& id, const std::wstring& value);
|
||||
|
||||
void AppendString(wstring str);
|
||||
void AppendWORD(WORD word);
|
||||
void AppendBytes(BYTE* ptr, size_t len);
|
||||
/**
|
||||
* Replaces existing VS_VERSIONINFO structure in the file locked
|
||||
* with the passed in ResourceEditor::FileLock instance with data
|
||||
* configured for this instance.
|
||||
*/
|
||||
const VersionInfo& apply(const ResourceEditor::FileLock& fileLock) const;
|
||||
|
||||
void ReplaceWORD(size_t offset, WORD word);
|
||||
void ReplaceBytes(size_t offset, BYTE* ptr, size_t len);
|
||||
|
||||
void Align(size_t bytesNumber);
|
||||
VersionInfo& apply(const ResourceEditor::FileLock& fileLock) {
|
||||
static_cast<const VersionInfo&>(*this).apply(fileLock);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
vector<BYTE> buffer;
|
||||
void fillBuffer(std::ostream& buf) const;
|
||||
|
||||
VS_FIXEDFILEINFO createFIXEDFILEINFO() const;
|
||||
|
||||
typedef std::map<std::wstring, std::wstring> PropertyMap;
|
||||
|
||||
PropertyMap props;
|
||||
};
|
||||
|
||||
#endif // BYTEBUFFER_H
|
||||
|
||||
#endif // #ifndef VERSIONINFO_H
|
@ -1,285 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include "VersionInfoSwap.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <tchar.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <Strsafe.h>
|
||||
#include <fstream>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
|
||||
using namespace std;
|
||||
|
||||
/*
|
||||
* [Property file] contains key/value pairs
|
||||
* The swap tool uses these pairs to create new version resource
|
||||
*
|
||||
* See MSDN docs for VS_VERSIONINFO structure that
|
||||
* depicts organization of data in this version resource
|
||||
* https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx
|
||||
*
|
||||
* The swap tool makes changes in [Executable file]
|
||||
* The tool assumes that the executable file has no version resource
|
||||
* and it adds new resource in the executable file.
|
||||
* If the executable file has an existing version resource, then
|
||||
* the existing version resource will be replaced with new one.
|
||||
*/
|
||||
|
||||
VersionInfoSwap::VersionInfoSwap(wstring executableProperties,
|
||||
wstring launcher) {
|
||||
m_executableProperties = executableProperties;
|
||||
m_launcher = launcher;
|
||||
}
|
||||
|
||||
bool VersionInfoSwap::PatchExecutable() {
|
||||
bool b = LoadFromPropertyFile();
|
||||
if (!b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ByteBuffer buf;
|
||||
b = CreateNewResource(&buf);
|
||||
if (!b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
b = this->UpdateResource(buf.getPtr(), static_cast<DWORD> (buf.getPos()));
|
||||
if (!b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VersionInfoSwap::LoadFromPropertyFile() {
|
||||
wifstream stream(m_executableProperties.c_str());
|
||||
|
||||
const locale empty_locale = locale::empty();
|
||||
const locale utf8_locale =
|
||||
locale(empty_locale, new codecvt_utf8<wchar_t>());
|
||||
stream.imbue(utf8_locale);
|
||||
|
||||
if (stream.is_open() == true) {
|
||||
int lineNumber = 1;
|
||||
while (stream.eof() == false) {
|
||||
wstring line;
|
||||
getline(stream, line);
|
||||
|
||||
// # at the first character will comment out the line.
|
||||
if (line.empty() == false && line[0] != '#') {
|
||||
wstring::size_type pos = line.find('=');
|
||||
if (pos != wstring::npos) {
|
||||
wstring name = line.substr(0, pos);
|
||||
wstring value = line.substr(pos + 1);
|
||||
m_props[name] = value;
|
||||
}
|
||||
}
|
||||
lineNumber++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates new version resource
|
||||
*
|
||||
* MSND docs for VS_VERSION_INFO structure
|
||||
* https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx
|
||||
*/
|
||||
bool VersionInfoSwap::CreateNewResource(ByteBuffer *buf) {
|
||||
size_t versionInfoStart = buf->getPos();
|
||||
buf->AppendWORD(0);
|
||||
buf->AppendWORD(sizeof VS_FIXEDFILEINFO);
|
||||
buf->AppendWORD(0);
|
||||
buf->AppendString(TEXT("VS_VERSION_INFO"));
|
||||
buf->Align(4);
|
||||
|
||||
VS_FIXEDFILEINFO fxi;
|
||||
if (!FillFixedFileInfo(&fxi)) {
|
||||
return false;
|
||||
}
|
||||
buf->AppendBytes((BYTE*) & fxi, sizeof (VS_FIXEDFILEINFO));
|
||||
buf->Align(4);
|
||||
|
||||
// String File Info
|
||||
size_t stringFileInfoStart = buf->getPos();
|
||||
buf->AppendWORD(0);
|
||||
buf->AppendWORD(0);
|
||||
buf->AppendWORD(1);
|
||||
buf->AppendString(TEXT("StringFileInfo"));
|
||||
buf->Align(4);
|
||||
|
||||
// String Table
|
||||
size_t stringTableStart = buf->getPos();
|
||||
buf->AppendWORD(0);
|
||||
buf->AppendWORD(0);
|
||||
buf->AppendWORD(1);
|
||||
|
||||
// "040904B0" = LANG_ENGLISH/SUBLANG_ENGLISH_US, Unicode CP
|
||||
buf->AppendString(TEXT("040904B0"));
|
||||
buf->Align(4);
|
||||
|
||||
// Strings
|
||||
vector<wstring> keys;
|
||||
for (map<wstring, wstring>::const_iterator it =
|
||||
m_props.begin(); it != m_props.end(); ++it) {
|
||||
keys.push_back(it->first);
|
||||
}
|
||||
|
||||
for (size_t index = 0; index < keys.size(); index++) {
|
||||
wstring name = keys[index];
|
||||
wstring value = m_props[name];
|
||||
|
||||
size_t stringStart = buf->getPos();
|
||||
buf->AppendWORD(0);
|
||||
buf->AppendWORD(static_cast<WORD> (value.length()));
|
||||
buf->AppendWORD(1);
|
||||
buf->AppendString(name);
|
||||
buf->Align(4);
|
||||
buf->AppendString(value);
|
||||
buf->ReplaceWORD(stringStart,
|
||||
static_cast<WORD> (buf->getPos() - stringStart));
|
||||
buf->Align(4);
|
||||
}
|
||||
|
||||
buf->ReplaceWORD(stringTableStart,
|
||||
static_cast<WORD> (buf->getPos() - stringTableStart));
|
||||
buf->ReplaceWORD(stringFileInfoStart,
|
||||
static_cast<WORD> (buf->getPos() - stringFileInfoStart));
|
||||
|
||||
// VarFileInfo
|
||||
size_t varFileInfoStart = buf->getPos();
|
||||
buf->AppendWORD(1);
|
||||
buf->AppendWORD(0);
|
||||
buf->AppendWORD(1);
|
||||
buf->AppendString(TEXT("VarFileInfo"));
|
||||
buf->Align(4);
|
||||
|
||||
buf->AppendWORD(0x24);
|
||||
buf->AppendWORD(0x04);
|
||||
buf->AppendWORD(0x00);
|
||||
buf->AppendString(TEXT("Translation"));
|
||||
buf->Align(4);
|
||||
// "000004B0" = LANG_NEUTRAL/SUBLANG_ENGLISH_US, Unicode CP
|
||||
buf->AppendWORD(0x0000);
|
||||
buf->AppendWORD(0x04B0);
|
||||
|
||||
buf->ReplaceWORD(varFileInfoStart,
|
||||
static_cast<WORD> (buf->getPos() - varFileInfoStart));
|
||||
buf->ReplaceWORD(versionInfoStart,
|
||||
static_cast<WORD> (buf->getPos() - versionInfoStart));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VersionInfoSwap::FillFixedFileInfo(VS_FIXEDFILEINFO *fxi) {
|
||||
wstring fileVersion;
|
||||
wstring productVersion;
|
||||
int ret;
|
||||
|
||||
fileVersion = m_props[TEXT("FileVersion")];
|
||||
productVersion = m_props[TEXT("ProductVersion")];
|
||||
|
||||
unsigned fv_1 = 0, fv_2 = 0, fv_3 = 0, fv_4 = 0;
|
||||
unsigned pv_1 = 0, pv_2 = 0, pv_3 = 0, pv_4 = 0;
|
||||
|
||||
ret = _stscanf_s(fileVersion.c_str(),
|
||||
TEXT("%d.%d.%d.%d"), &fv_1, &fv_2, &fv_3, &fv_4);
|
||||
if (ret <= 0 || ret > 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = _stscanf_s(productVersion.c_str(),
|
||||
TEXT("%d.%d.%d.%d"), &pv_1, &pv_2, &pv_3, &pv_4);
|
||||
if (ret <= 0 || ret > 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fxi->dwSignature = 0xFEEF04BD;
|
||||
fxi->dwStrucVersion = 0x00010000;
|
||||
|
||||
fxi->dwFileVersionMS = MAKELONG(fv_2, fv_1);
|
||||
fxi->dwFileVersionLS = MAKELONG(fv_4, fv_3);
|
||||
fxi->dwProductVersionMS = MAKELONG(pv_2, pv_1);
|
||||
fxi->dwProductVersionLS = MAKELONG(pv_4, pv_3);
|
||||
|
||||
fxi->dwFileFlagsMask = 0;
|
||||
fxi->dwFileFlags = 0;
|
||||
fxi->dwFileOS = VOS_NT_WINDOWS32;
|
||||
|
||||
wstring exeExt =
|
||||
m_launcher.substr(m_launcher.find_last_of(TEXT(".")));
|
||||
if (exeExt == TEXT(".exe")) {
|
||||
fxi->dwFileType = VFT_APP;
|
||||
} else if (exeExt == TEXT(".dll")) {
|
||||
fxi->dwFileType = VFT_DLL;
|
||||
} else {
|
||||
fxi->dwFileType = VFT_UNKNOWN;
|
||||
}
|
||||
fxi->dwFileSubtype = 0;
|
||||
|
||||
fxi->dwFileDateLS = 0;
|
||||
fxi->dwFileDateMS = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds new resource in the executable
|
||||
*/
|
||||
bool VersionInfoSwap::UpdateResource(LPVOID lpResLock, DWORD size) {
|
||||
|
||||
HANDLE hUpdateRes;
|
||||
BOOL r;
|
||||
|
||||
hUpdateRes = ::BeginUpdateResource(m_launcher.c_str(), FALSE);
|
||||
if (hUpdateRes == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
r = ::UpdateResource(hUpdateRes,
|
||||
RT_VERSION,
|
||||
MAKEINTRESOURCE(VS_VERSION_INFO),
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
|
||||
lpResLock,
|
||||
size);
|
||||
|
||||
if (!r) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!::EndUpdateResource(hUpdateRes, FALSE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -23,17 +23,50 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include <Windows.h>
|
||||
#include <tchar.h>
|
||||
#include <strsafe.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include "Utils.h"
|
||||
#include "JniUtils.h"
|
||||
#include "FileUtils.h"
|
||||
#include "ErrorHandling.h"
|
||||
|
||||
#pragma comment(lib, "advapi32")
|
||||
|
||||
// Max value name size per MSDN plus NULL
|
||||
#define VALUE_NAME_SIZE 16384
|
||||
namespace {
|
||||
|
||||
std::wstring GetLongPath(const std::wstring& path) {
|
||||
const std::wstring cleanPath = FileUtils::removeTrailingSlash(path);
|
||||
if (cleanPath.size() != path.size()) {
|
||||
return GetLongPath(cleanPath);
|
||||
}
|
||||
|
||||
enum { BUFFER_SIZE = 4096 };
|
||||
|
||||
std::wstring result;
|
||||
|
||||
TCHAR *pBuffer = new TCHAR[BUFFER_SIZE];
|
||||
if (pBuffer != NULL) {
|
||||
DWORD dwResult = GetLongPathName(path.c_str(), pBuffer, BUFFER_SIZE);
|
||||
if (dwResult > 0 && dwResult < BUFFER_SIZE) {
|
||||
result = std::wstring(pBuffer);
|
||||
} else {
|
||||
delete [] pBuffer;
|
||||
pBuffer = new TCHAR[dwResult];
|
||||
if (pBuffer != NULL) {
|
||||
DWORD dwResult2 =
|
||||
GetLongPathName(path.c_str(), pBuffer, dwResult);
|
||||
if (dwResult2 == (dwResult - 1)) {
|
||||
result = std::wstring(pBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pBuffer != NULL) {
|
||||
delete [] pBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@ -52,12 +85,14 @@ extern "C" {
|
||||
jstring jValue, jint defaultValue) {
|
||||
jint jResult = defaultValue;
|
||||
|
||||
JP_TRY;
|
||||
|
||||
if (key != jdk_incubator_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE) {
|
||||
return jResult;
|
||||
JP_THROW("Inavlid Windows registry key id");
|
||||
}
|
||||
|
||||
wstring subKey = GetStringFromJString(pEnv, jSubKey);
|
||||
wstring value = GetStringFromJString(pEnv, jValue);
|
||||
const std::wstring subKey = jni::toUnicodeString(pEnv, jSubKey);
|
||||
const std::wstring value = jni::toUnicodeString(pEnv, jValue);
|
||||
|
||||
HKEY hSubKey = NULL;
|
||||
LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, subKey.c_str(), 0,
|
||||
@ -74,6 +109,8 @@ extern "C" {
|
||||
RegCloseKey(hSubKey);
|
||||
}
|
||||
|
||||
JP_CATCH_ALL;
|
||||
|
||||
return jResult;
|
||||
}
|
||||
|
||||
@ -85,11 +122,14 @@ extern "C" {
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_jdk_incubator_jpackage_internal_WindowsRegistry_openRegistryKey(
|
||||
JNIEnv *pEnv, jclass c, jint key, jstring jSubKey) {
|
||||
|
||||
JP_TRY;
|
||||
|
||||
if (key != jdk_incubator_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE) {
|
||||
return 0;
|
||||
JP_THROW("Inavlid Windows registry key id");
|
||||
}
|
||||
|
||||
wstring subKey = GetStringFromJString(pEnv, jSubKey);
|
||||
const std::wstring subKey = jni::toUnicodeString(pEnv, jSubKey);
|
||||
HKEY hSubKey = NULL;
|
||||
LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, subKey.c_str(), 0,
|
||||
KEY_QUERY_VALUE, &hSubKey);
|
||||
@ -97,6 +137,8 @@ extern "C" {
|
||||
return (jlong)hSubKey;
|
||||
}
|
||||
|
||||
JP_CATCH_ALL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -108,6 +150,12 @@ extern "C" {
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_jdk_incubator_jpackage_internal_WindowsRegistry_enumRegistryValue(
|
||||
JNIEnv *pEnv, jclass c, jlong lKey, jint jIndex) {
|
||||
|
||||
JP_TRY;
|
||||
|
||||
// Max value name size per MSDN plus NULL
|
||||
enum { VALUE_NAME_SIZE = 16384 };
|
||||
|
||||
HKEY hKey = (HKEY)lKey;
|
||||
TCHAR valueName[VALUE_NAME_SIZE] = {0}; // Max size per MSDN plus NULL
|
||||
DWORD cchValueName = VALUE_NAME_SIZE;
|
||||
@ -117,10 +165,12 @@ extern "C" {
|
||||
size_t chLength = 0;
|
||||
if (StringCchLength(valueName, VALUE_NAME_SIZE, &chLength)
|
||||
== S_OK) {
|
||||
return GetJStringFromString(pEnv, valueName, (jsize)chLength);
|
||||
return jni::toJString(pEnv, std::wstring(valueName, chLength));
|
||||
}
|
||||
}
|
||||
|
||||
JP_CATCH_ALL;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -144,24 +194,25 @@ extern "C" {
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_jdk_incubator_jpackage_internal_WindowsRegistry_comparePaths(
|
||||
JNIEnv *pEnv, jclass c, jstring jPath1, jstring jPath2) {
|
||||
wstring path1 = GetStringFromJString(pEnv, jPath1);
|
||||
wstring path2 = GetStringFromJString(pEnv, jPath2);
|
||||
|
||||
JP_TRY;
|
||||
|
||||
std::wstring path1 = jni::toUnicodeString(pEnv, jPath1);
|
||||
std::wstring path2 = jni::toUnicodeString(pEnv, jPath2);
|
||||
|
||||
path1 = GetLongPath(path1);
|
||||
path2 = GetLongPath(path2);
|
||||
|
||||
if (path1.length() == 0 || path2.length() == 0) {
|
||||
if (path1.empty() || path2.empty()) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if (path1.length() != path2.length()) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if (_tcsnicmp(path1.c_str(), path2.c_str(), path1.length()) == 0) {
|
||||
if (tstrings::equals(path1, path2, tstrings::IGNORE_CASE)) {
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
JP_CATCH_ALL;
|
||||
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
|
@ -23,80 +23,125 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <windows.h>
|
||||
|
||||
#include "ResourceEditor.h"
|
||||
#include "WinErrorHandling.h"
|
||||
#include "ErrorHandling.h"
|
||||
#include "IconSwap.h"
|
||||
#include "VersionInfoSwap.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace std;
|
||||
#include "VersionInfo.h"
|
||||
#include "JniUtils.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Class: jdk_incubator_jpackage_internal_WindowsAppImageBuilder
|
||||
* Class: jdk_incubator_jpackage_internal_ExecutableRebrander
|
||||
* Method: lockResource
|
||||
* Signature: (Ljava/lang/String;)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_jdk_incubator_jpackage_internal_ExecutableRebrander_lockResource(
|
||||
JNIEnv *pEnv, jclass c, jstring jExecutable) {
|
||||
|
||||
JP_TRY;
|
||||
|
||||
const std::wstring executable = jni::toUnicodeString(pEnv, jExecutable);
|
||||
|
||||
return reinterpret_cast<jlong>(
|
||||
ResourceEditor::FileLock(executable).ownHandle(false).get());
|
||||
|
||||
JP_CATCH_ALL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: jdk_incubator_jpackage_internal_ExecutableRebrander
|
||||
* Method: unlockResource
|
||||
* Signature: (J;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_jdk_incubator_jpackage_internal_ExecutableRebrander_unlockResource(
|
||||
JNIEnv *pEnv, jclass c, jlong jResourceLock) {
|
||||
|
||||
JP_TRY;
|
||||
ResourceEditor::FileLock(
|
||||
reinterpret_cast<HANDLE>(jResourceLock)).ownHandle(true);
|
||||
JP_CATCH_ALL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: jdk_incubator_jpackage_internal_ExecutableRebrander
|
||||
* Method: iconSwap
|
||||
* Signature: (Ljava/lang/String;Ljava/lang/String;)I
|
||||
* Signature: (J;Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_jdk_incubator_jpackage_internal_WindowsAppImageBuilder_iconSwap(
|
||||
JNIEnv *pEnv, jclass c, jstring jIconTarget, jstring jLauncher) {
|
||||
wstring iconTarget = GetStringFromJString(pEnv, jIconTarget);
|
||||
wstring launcher = GetStringFromJString(pEnv, jLauncher);
|
||||
Java_jdk_incubator_jpackage_internal_ExecutableRebrander_iconSwap(
|
||||
JNIEnv *pEnv, jclass c, jlong jResourceLock, jstring jIconTarget) {
|
||||
|
||||
if (ChangeIcon(iconTarget, launcher)) {
|
||||
JP_TRY;
|
||||
|
||||
const ResourceEditor::FileLock lock(reinterpret_cast<HANDLE>(jResourceLock));
|
||||
|
||||
const std::wstring iconTarget = jni::toUnicodeString(pEnv, jIconTarget);
|
||||
|
||||
if (ChangeIcon(lock.get(), iconTarget)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
JP_CATCH_ALL;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: jdk_incubator_jpackage_internal_WindowsAppImageBuilder
|
||||
* Class: jdk_incubator_jpackage_internal_ExecutableRebrander
|
||||
* Method: versionSwap
|
||||
* Signature: (Ljava/lang/String;Ljava/lang/String;)I
|
||||
* Signature: (J;[Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_jdk_incubator_jpackage_internal_WindowsAppImageBuilder_versionSwap(
|
||||
JNIEnv *pEnv, jclass c, jstring jExecutableProperties,
|
||||
jstring jLauncher) {
|
||||
Java_jdk_incubator_jpackage_internal_ExecutableRebrander_versionSwap(
|
||||
JNIEnv *pEnv, jclass c, jlong jResourceLock,
|
||||
jobjectArray jExecutableProperties) {
|
||||
|
||||
wstring executableProperties = GetStringFromJString(pEnv,
|
||||
JP_TRY;
|
||||
|
||||
const tstring_array props = jni::toUnicodeStringArray(pEnv,
|
||||
jExecutableProperties);
|
||||
wstring launcher = GetStringFromJString(pEnv, jLauncher);
|
||||
|
||||
VersionInfoSwap vs(executableProperties, launcher);
|
||||
if (vs.PatchExecutable()) {
|
||||
return 0;
|
||||
VersionInfo vi;
|
||||
|
||||
tstring_array::const_iterator it = props.begin();
|
||||
tstring_array::const_iterator end = props.end();
|
||||
for (; it != end; ++it) {
|
||||
const tstring name = *it;
|
||||
const tstring value = *++it;
|
||||
vi.setProperty(name, value);
|
||||
}
|
||||
|
||||
const ResourceEditor::FileLock lock(reinterpret_cast<HANDLE>(jResourceLock));
|
||||
vi.apply(lock);
|
||||
|
||||
return 0;
|
||||
|
||||
JP_CATCH_ALL;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: jdk_incubator_jpackage_internal_WinExeBundler
|
||||
* Method: embedMSI
|
||||
* Signature: (Ljava/lang/String;Ljava/lang/String;)I
|
||||
* Signature: (J;Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_jdk_incubator_jpackage_internal_WinExeBundler_embedMSI(
|
||||
JNIEnv *pEnv, jclass c, jstring jexePath, jstring jmsiPath) {
|
||||
|
||||
const wstring exePath = GetStringFromJString(pEnv, jexePath);
|
||||
const wstring msiPath = GetStringFromJString(pEnv, jmsiPath);
|
||||
JNIEnv *pEnv, jclass c, jlong jResourceLock, jstring jmsiPath) {
|
||||
|
||||
JP_TRY;
|
||||
|
||||
ResourceEditor()
|
||||
.id(L"msi")
|
||||
.type(RT_RCDATA)
|
||||
.apply(ResourceEditor::FileLock(exePath), msiPath);
|
||||
const std::wstring msiPath = jni::toUnicodeString(pEnv, jmsiPath);
|
||||
|
||||
const ResourceEditor::FileLock lock(reinterpret_cast<HANDLE>(jResourceLock));
|
||||
ResourceEditor().id(L"msi").type(RT_RCDATA).apply(lock, msiPath);
|
||||
|
||||
return 0;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user