Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added AssemblyPublicizer.exe
Binary file not shown.
253 changes: 66 additions & 187 deletions AssemblyPublicizer/AssemblyPublicizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Mono.Cecil;
using Mono.Options;

/// <summary>
/// AssemblyPublicizer - A tool to create a copy of an assembly in
Expand Down Expand Up @@ -34,7 +32,7 @@
namespace CabbageCrow.AssemblyPublicizer
{
/// <summary>
/// Creates a copy of an assembly in which all members are public (types, methods, fields, getters and setters of properties).
/// Creates a copy of an assembly in which all members are public (types, methods, fields (Minus Events), getters and setters of properties).
/// If you use the modified assembly as your reference and compile your dll with the option "Allow unsafe code" enabled,
/// you can access all private elements even when using the original assembly.
/// Without "Allow unsafe code" you get an access violation exception during runtime when accessing private members except for types.
Expand All @@ -47,207 +45,88 @@ namespace CabbageCrow.AssemblyPublicizer
/// </summary>
class AssemblyPublicizer
{
static bool automaticExit, help;

static void Main(string[] args)
{
var suffix = "_publicized";
var defaultOutputDir = "publicized_assemblies";

var input = "";
string output = "";

var options = new OptionSet
{
{ "i|input=", "Path (relative or absolute) to the input assembly", i => input = i },
{ "o|output=", "Path/dir/filename for the output assembly", o => output = o },
{ "e|exit", "Application should automatically exit", e => automaticExit = e != null},
{ "h|help", "Show this message", h => help = h != null}
};


Console.WriteLine();

List<string> extra;
try
{
// parse the command line
extra = options.Parse(args);

if (help)
ShowHelp(options);

if (input == "" && extra.Count() >= 1)
input = extra[0];

if (input == "")
throw new OptionException();

if (output == "" && extra.Count() >= 2)
output = extra[1];
}
catch (OptionException)
{
// output some error message
Console.WriteLine("ERROR! Incorrect arguments. You need to provide the path to the assembly to publicize.");
Console.WriteLine("On Windows you can even drag and drop the assembly on the .exe.");
Console.WriteLine("Try `--help' for more information.");
Exit(10);
}


var inputFile = input;
AssemblyDefinition assembly = null;
string outputPath = "", outputName = "";


if (output != "")
int count = 0;
foreach(string input in args)
{
try
{
outputPath = Path.GetDirectoryName(output);
outputName = Path.GetFileName(output);
}
catch(Exception)
{
Console.WriteLine("ERROR! Invalid output argument.");
Exit(20);
}
}


if (!File.Exists(inputFile))
{
Console.WriteLine();
Console.WriteLine("ERROR! File doesn't exist or you don't have sufficient permissions.");
Exit(30);
}

try
{
assembly = AssemblyDefinition.ReadAssembly(inputFile);
}
catch (Exception)
{
Console.WriteLine();
Console.WriteLine("ERROR! Cannot read the assembly. Please check your permissions.");
Exit(40);
}


var allTypes = GetAllTypes(assembly.MainModule);
var allMethods = allTypes.SelectMany(t => t.Methods);
var allFields = allTypes.SelectMany(t => t.Fields);

int count;
string reportString = "Changed {0} {1} to public.";

#region Make everything public

count = 0;
foreach (var type in allTypes)
{
if (!type?.IsPublic ?? false && !type.IsNestedPublic)
{
count++;
if (type.IsNested)
type.IsNestedPublic = true;
else
type.IsPublic = true;
}
}
Console.WriteLine(reportString, count, "types");

count = 0;
foreach (var method in allMethods)
{
if (!method?.IsPublic ?? false)
{
count++;
method.IsPublic = true;
AssemblyDefinition assembly = null;

if(!File.Exists((string)input))
continue;

assembly = AssemblyDefinition.ReadAssembly((string)input);

var allTypes = GetAllTypes(assembly.MainModule);
var allMethods = allTypes.SelectMany(t => t.Methods);
var allFields = FilterBackingEventFields(allTypes);


#region Make everything public

foreach(var type in allTypes)
{
if((!type?.IsPublic ?? false) || (!type?.IsNestedPublic ?? false))
{
if(type.IsNested)
type.IsNestedPublic = true;
else
type.IsPublic = true;
}
}

foreach(MethodDefinition method in allMethods)
{
if(!method?.IsPublic ?? false)
{
method.IsPublic = true;
}
method.Body = new Mono.Cecil.Cil.MethodBody(new MethodDefinition(method.Name, method.Attributes, method.ReturnType));
}

foreach(var field in allFields)
{
if(!field?.IsPublic ?? false)
{
field.IsPublic = true;
}
}
#endregion

var outputName = string.Format("{0}{1}{2}",
Path.GetFileNameWithoutExtension((string)input), "", Path.GetExtension((string)input));
var outputPath = defaultOutputDir;
var outputFile = Path.Combine(outputPath, outputName);

if(outputPath != "" && !Directory.Exists(outputPath))
Directory.CreateDirectory(outputPath);
assembly.Write(outputFile);
}
}
Console.WriteLine(reportString, count, "methods (including getters and setters)");

count = 0;
foreach (var field in allFields)
{
if (!field?.IsPublic ?? false)
catch(Exception e)
{
count++;
field.IsPublic = true;
Console.WriteLine($"{input} failed with Exception\n{e}");
}
}
Console.WriteLine(reportString, count, "fields");

#endregion


Console.WriteLine();

if (outputName == "")
{
outputName = String.Format("{0}{1}{2}",
Path.GetFileNameWithoutExtension(inputFile), suffix, Path.GetExtension(inputFile));
Console.WriteLine(@"Info: Use default output name: ""{0}""", outputName);
}

if(outputPath == "")
{
outputPath = defaultOutputDir;
Console.WriteLine(@"Info: Use default output dir: ""{0}""", outputPath);
}

Console.WriteLine("Saving a copy of the modified assembly ...");

var outputFile = Path.Combine(outputPath, outputName);

try
{
if (outputPath != "" && !Directory.Exists(outputPath))
Directory.CreateDirectory(outputPath);
assembly.Write(outputFile);
}
catch (Exception)
{
Console.WriteLine();
Console.WriteLine("ERROR! Cannot create/overwrite the new assembly. ");
Console.WriteLine("Please check the path and its permissions " +
"and in case of overwriting an existing file ensure that it isn't currently used.");
Exit(50);
}

Console.WriteLine("Completed.");
Console.WriteLine();
Console.WriteLine("Use the publicized library as your reference and compile your dll with the ");
Console.WriteLine(@"option ""Allow unsafe code"" enabled.");
Console.WriteLine(@"Without it you get an access violation exception during runtime when accessing");
Console.WriteLine("private members except for types.");
Exit(0);
Exit(count);
}

public static void Exit(int exitCode = 0)
public static void Exit(int exitCode)
{
if (!automaticExit)
{
Console.WriteLine();
Console.WriteLine("Press any key to exit ...");
if(exitCode != 0)
Console.ReadKey();
}

Environment.Exit(exitCode);
}

private static void ShowHelp(OptionSet p)
public static IEnumerable<FieldDefinition> FilterBackingEventFields(IEnumerable<TypeDefinition> allTypes)
{
Console.WriteLine("Usage: AssemblyPublicizer.exe [Options]+");
Console.WriteLine("Creates a copy of an assembly in which all members are public.");
Console.WriteLine("An input path must be provided, the other options are optional.");
Console.WriteLine("You can use it without the option identifiers;");
Console.WriteLine("If so, the first argument is for input and the optional second one for output.");
Console.WriteLine();
Console.WriteLine("Options:");
p.WriteOptionDescriptions(Console.Out);
Exit(0);
List<string> eventNames = allTypes.SelectMany(t => t.Events).Select(eventDefinition => eventDefinition.Name).ToList();

return allTypes.SelectMany(x => x.Fields).Where(fieldDefinition => !eventNames.Contains(fieldDefinition.Name));
}

/// <summary>
Expand All @@ -269,12 +148,12 @@ private static IEnumerable<TypeDefinition> _GetAllNestedTypes(IEnumerable<TypeDe
{
//return typeDefinitions.SelectMany(t => t.NestedTypes);

if (typeDefinitions?.Count() == 0)
if(typeDefinitions?.Count() == 0)
return new List<TypeDefinition>();

var result = typeDefinitions.Concat(_GetAllNestedTypes(typeDefinitions.SelectMany(t => t.NestedTypes)));

return result;
return result;
}


Expand Down
39 changes: 21 additions & 18 deletions AssemblyPublicizer/AssemblyPublicizer.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
Expand All @@ -8,7 +9,7 @@
<OutputType>Exe</OutputType>
<RootNamespace>CabbageCrow.AssemblyPublicizer</RootNamespace>
<AssemblyName>AssemblyPublicizer</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
Expand All @@ -28,7 +29,7 @@
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<DebugType>embedded</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
Expand All @@ -38,27 +39,21 @@
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Cecil, Version=0.10.0.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.10.0\lib\net40\Mono.Cecil.dll</HintPath>
<EmbedAssembly>true</EmbedAssembly>
<Reference Include="Costura, Version=4.1.0.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
<HintPath>..\packages\Costura.Fody.4.1.0\lib\net40\Costura.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Mono.Cecil.Mdb, Version=0.10.0.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.10.0\lib\net40\Mono.Cecil.Mdb.dll</HintPath>
<Private>False</Private>
<Reference Include="Mono.Cecil, Version=0.11.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.11.3\lib\net40\Mono.Cecil.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Pdb, Version=0.10.0.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.10.0\lib\net40\Mono.Cecil.Pdb.dll</HintPath>
<Private>False</Private>
<Reference Include="Mono.Cecil.Mdb, Version=0.11.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.11.3\lib\net40\Mono.Cecil.Mdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Rocks, Version=0.10.0.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.10.0\lib\net40\Mono.Cecil.Rocks.dll</HintPath>
<Private>False</Private>
<Reference Include="Mono.Cecil.Pdb, Version=0.11.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.11.3\lib\net40\Mono.Cecil.Pdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Options, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Options.5.3.0.1\lib\net4-client\Mono.Options.dll</HintPath>
<EmbedAssembly>true</EmbedAssembly>
<Private>False</Private>
<Reference Include="Mono.Cecil.Rocks, Version=0.11.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.11.3\lib\net40\Mono.Cecil.Rocks.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
Expand Down Expand Up @@ -90,4 +85,12 @@
<PostBuildEvent>XCOPY "$(TargetDir)Licenses\*" "$(SolutionPath)\..\..\AssemblyPublicizer\Licenses\*" /S /Y
copy "$(TargetPath)" "$(SolutionPath)\..\..\AssemblyPublicizer\"</PostBuildEvent>
</PropertyGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props'))" />
<Error Condition="!Exists('..\packages\Fody.6.2.6\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.6.2.6\build\Fody.targets'))" />
</Target>
<Import Project="..\packages\Fody.6.2.6\build\Fody.targets" Condition="Exists('..\packages\Fody.6.2.6\build\Fody.targets')" />
</Project>
3 changes: 3 additions & 0 deletions AssemblyPublicizer/FodyWeavers.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Costura />
</Weavers>
Loading