New build script

Remove Amalgamator, CompileTool, and old insert.bat.

Also fix some path issues with ScriptTool.
This commit is contained in:
jeffman 2019-01-06 19:40:02 -05:00
parent fec378462d
commit 61f12d6151
18 changed files with 478 additions and 532 deletions

View File

@ -1,3 +1,2 @@
dotnet build tools/ScriptTool -o bin/ScriptTool
dotnet build tools/SymbolTableBuilder -o bin/SymbolTableBuilder
dotnet build tools/CompileTool -o bin/CompileTool

441
build.ps1 Normal file
View File

@ -0,0 +1,441 @@
#Region Variables
$input_rom_file = "bin/m12fresh.gba"
$output_rom_file = "bin/m12.gba"
$eb_rom_file = "bin/eb.smc"
$working_dir = "working"
$src_dir = "src"
$input_c_files =
"src/c/ext.c",
"src/c/vwf.c"
$base_c_address = 0x8100000;
$output_rom_sym_file = [IO.Path]::ChangeExtension($output_rom_file, "sym")
$scripttool_cmd = "bin/ScriptTool/ScriptTool.dll"
$scripttool_args =
"-compile",
"-main",
"-misc",
$working_dir,
$eb_rom_file,
$input_rom_file
If ($IsWindows) { $asm_cmd = "bin/armips.exe" }
ElseIf ($IsLinux) { $asm_cmd = "bin/armips" }
Else
{
Write-Host "TODO: what's the Mac version of this?"
Exit -1
}
$symbuilder_cmd = "bin/SymbolTableBuilder/SymbolTableBuilder.dll"
# armips will be rooted in working_dir for these, so the includes files have an implicit "working/" in front
$includes_asm_file = "m12-includes.asm"
$includes_sym_file = [IO.Path]::ChangeExtension($includes_asm_file, "sym")
$gcc_cmd = "arm-none-eabi-gcc"
$gcc_args =
"-c",
"-O3",
"-fno-ipa-cp",
"-fno-inline",
"-march=armv4t",
"-mtune=arm7tdmi",
"-mthumb",
"-ffixed-r12",
"-mno-long-calls"
$ld_cmd = "arm-none-eabi-ld"
$combined_obj_file = "src/c/combined.o"
$linked_obj_file = "src/c/linked.o"
$combine_script = "src/c/combine.ld"
$link_script = "src/c/link.ld"
$undefine_obj_file = "src/c/ext.o"
$combine_script_contents =
"SECTIONS { .text 0x$($base_c_address.ToString('X')) : { *(.text .rodata) } }"
$link_script_contents =
"SECTIONS { .text 0x$($base_c_address.ToString('X')) : { *(.text .data .rodata) } }"
$objdump_cmd = "arm-none-eabi-objdump"
$compiled_asm_file = "src/m2-compiled.asm"
# armips will be rooted in src_dir for these, so the includes files have an implicit "working/" in front
$hack_asm_file = "m2-hack.asm"
$hack_sym_file = [IO.Path]::ChangeExtension($hack_asm_file, "sym")
$readelf_cmd = "arm-none-eabi-readelf"
#EndRegion Variables
#Region Functions
class Symbol
{
[string]$Name
[int]$Value
[int]$Size
[bool]$IsLocal
[bool]$IsGlobal
[bool]$IsWeak
[bool]$IsConstructor
[bool]$IsWarning
[bool]$IsIndirect
[bool]$IsDebugging
[bool]$IsDynamic
[bool]$IsFunction
[bool]$IsFile
[bool]$IsObject
[string]$Section
[bool]$IsAbsolute
[bool]$IsUndefined
}
class SectionInfo
{
[string]$Name
[int]$Address
[int]$Offset
[int]$Size
}
Function Get-Symbols ([string]$obj_file)
{
return & $objdump_cmd -t $obj_file | ForEach-Object { New-Symbol $_ } | Where-Object { $_ -ne $null }
}
# Converts a symbol from objdump's string representation to a rich object representation
$symbol_regex = "(?'value'[0-9a-fA-F]{8})\s(?'flags'.{7})\s(?'section'\S+)\s+(?'size'[0-9a-fA-F]{8})\s(?'name'\S+)"
Function New-Symbol([string]$symbol_string)
{
if ($symbol_string -match $symbol_regex)
{
$symbol = [Symbol]::new()
$symbol.Name = $Matches.name
$symbol.Value = [int]::Parse($Matches.value, [System.Globalization.NumberStyles]::HexNumber)
$symbol.Size = [int]::Parse($Matches.size, [System.Globalization.NumberStyles]::HexNumber)
$symbol.Section = $Matches.section
$symbol.IsAbsolute = $symbol.Section -eq "*ABS*"
$symbol.IsUndefined = $symbol.Section -eq "*UND*"
$flags = $Matches.flags
$symbol.IsLocal = $flags.Contains("l") -or $flags.Contains("!")
$symbol.IsGlobal = $flags.Contains("g") -or $flags.Contains("!")
$symbol.IsWeak = $flags.Contains("w")
$symbol.IsConstructor = $flags.Contains("C")
$symbol.IsWarning = $flags.Contains("W")
$symbol.IsIndirect = $flags.Contains("I")
$symbol.IsDebugging = $flags.Contains("d")
$symbol.IsDynamic = $flags.Contains("D")
$symbol.IsFunction = $flags.Contains("F")
$symbol.IsFile = $flags.Contains("f")
$symbol.IsObject = $flags.Contains("O")
return $symbol
}
else
{
return $null
}
}
Function Get-SymfileSymbols([string]$symbol_file)
{
return Get-Content $symbol_file | ForEach-Object { New-SymfileSymbol $_ } | Where-Object { $_ -ne $null }
}
$symfile_symbol_regex = "(?'value'[0-9a-fA-F]{8})\s+(?'name'(?>\.|@@|[a-zA-Z0-9_])[a-zA-Z0-9_]+):{0,1}(?'size'[0-9a-fA-F]+){0,1}"
Function New-SymfileSymbol([string]$symbol_string)
{
if ($symbol_string -match $symfile_symbol_regex)
{
$symbol = [Symbol]::new()
$symbol.Name = $Matches.name
$symbol.Value = [int]::Parse($Matches.value, [System.Globalization.NumberStyles]::HexNumber)
if ($null -ne $Matches.size)
{
$symbol.Size = [int]::Parse($Matches.size, [System.Globalization.NumberStyles]::HexNumber)
}
else
{
$symbol.Size = 0
}
$symbol.IsLocal = $symbol.Name.StartsWith("@@")
$symbol.IsGlobal = -not $symbol.Name.StartsWith(".") -and -not $symbol.IsLocal
return $symbol
}
else
{
return $null
}
}
function Get-SectionInfo([string]$object_file)
{
$hash = @{}
& $readelf_cmd -S $object_file | ForEach-Object {
$section = New-Section $_
if ($null -ne $section)
{
$hash[$section.Name] = $section
}
}
return $hash
}
$section_regex = "\s?\[\s?\d+]\s(?'name'\S+)\s+\S+\s+(?'address'[0-9a-fA-F]+)\s(?'offset'[0-9a-fA-F]+)\s(?'size'[0-9a-fA-F]+)"
function New-Section([string]$section_string)
{
if ($section_string -match $section_regex)
{
$section = [SectionInfo]::new()
$section.Name = $Matches.name
$section.Address = [int]::Parse($Matches.address, [System.Globalization.NumberStyles]::HexNumber)
$section.Offset = [int]::Parse($Matches.offset, [System.Globalization.NumberStyles]::HexNumber)
$section.Size = [int]::Parse($Matches.size, [System.Globalization.NumberStyles]::HexNumber)
return $section
}
else
{
return $null
}
}
#EndRegion Functions
<#
This is a complicated build script that does complicated things, but it's
that way for a reason.
- We want to use ASM and C code files simultaneously
- The ASM code defines symbols that we want to reference from C
- The C code defines symbols that we want to reference from ASM
- The game text defines symbols that we want to reference from C
The ASM and C code therefore depend on each other. The way around this catch-22
is to separate the compiling and linking stages of the C code.
1) Compile the game text
Inputs:
- Text/script files
Outputs:
- BIN files containing compiled text data
- ASM files that relocate text pointers (m12-includes.asm)
2) Assemble output from step 1
Inputs:
- Output from step 1
- Fresh M12 ROM file
Outputs:
- ROM file with text inserted and repointed
- m12-includes.sym file containing generated symbols (e.g. individual strings from m12-other.json)
3) Compile C code
Inputs:
- C files
Outputs:
- O files (one for each C file)
Remarks:
- All symbols not defined in the C code itself must be marked extern. We will link it
in a later step. This includes the string symbols from step 2.
- Due to an assembler limitation, extern symbols cannot contain capital letters.
- There's a weird quirk with the linker that requires extra care when using external
functions. You need to declare them as extern AND implement them using the ((naked))
attribute, e.g.
(in a header file) extern int m2_drawwindow(WINDOW* window);
(in a code file) int __attribute__((naked)) m2_drawwindow(WINDOW* window) {}
See: http://stackoverflow.com/a/43283331/1188632
This will cause a duplicate definition of the function symbol, since it's *really*
defined in the ASM files somewhere, but we're redefining it again in C. So we also need
to "undefine" these symbols later on when linking.
To make it a bit easier to do all that, place all such implementations in ext.c.
4) First link stage
Inputs:
- O files from step 3
- Base address
Outputs:
- Single O file positioned to the base address
- m2-compiled.asm, containing C symbol definitions
Remarks:
- This is an incremental link; there will still be undefined symbols.
- However, with this combined O file, the code layout will not change and we can now
define symbols from the C code to be used in the ASM code.
- The symbols will be passed to the assembler next, so export them as an ASM file
with one ".definelabel" entry for each defined symbol. They need to be halfword-aligned.
This file is called "m2-compiled.asm" by default and is referenced by m2-hack.asm.
- Exclude the symbols defined in ext.c from m2-compiled.asm.
5) Assemble ASM code
Inputs:
- m2-hack.asm
- m2-compiled.asm
- All other ASM and data files from src/ (but not the generated ones from working/)
- M12 ROM file from step 2
Outputs:
- M12 ROM file with all ASM code and data included
- m2-hack.sym with all symbols defined thusfar
6) Generate final linker script
Inputs:
- Base address (same as step 4)
- m2-hack.sym (from step 5)
- m12-includes.sym (from step 2)
Outputs:
- Linker script file
Remarks:
- The linker script must define each symbol that's still undefined; if everything is
happy at this point, then they should all be contained within the two input SYM files.
7) Final link stage
Inputs:
- O file from step 4
- Linker script from step 6
Outputs:
- Single O file with all symbols defined
8) Copy code to ROM
Inputs:
- O file from step 7
- M12 ROM file from step 5
Outputs:
- M12 ROM file with all code and data included
9) Build final symbol file
Inputs:
- m2-hack.sym
- m12-includes.sym
Outputs:
- m12.sym
Remarks:
- This is a merged symbol file for debugging convenience.
- The assembler generates a dummy .byt symbol at the base address; remove it.
- Use SymbolTableBuilder to do the merge, plus remove the undesirable namespaces that
the assembler inserts.
#>
# ------------------------- COMPILE GAME TEXT -----------------------
"Copying $input_rom_file to $output_rom_file..."
Copy-Item -Path $input_rom_file -Destination $output_rom_file
"Compiling game text..."
& dotnet $scripttool_cmd $scripttool_args
if ($LASTEXITCODE -ne 0) { exit -1 }
# ------------------------ ASSEMBLE GAME TEXT -----------------------
"Assembling game text..."
& $asm_cmd -root $working_dir -sym $includes_sym_file $includes_asm_file
if ($LASTEXITCODE -ne 0) { exit -1 }
# ----------------------------- COMPILE C ---------------------------
$obj_files = @()
# Invoke gcc on each file individually so that we can specify the output file
foreach ($input_c_file in $input_c_files)
{
$obj_file = [IO.Path]::ChangeExtension($input_c_file, "o")
$obj_files += $obj_file
"Compiling $input_c_file..."
& $gcc_cmd $gcc_args -o $obj_file $input_c_file
if ($LASTEXITCODE -ne 0) { exit -1 }
}
# ----------------------------- 1ST LINK ----------------------------
"Writing $combine_script..."
$combine_script_contents | Out-File -FilePath $combine_script
"Linking $obj_files..."
& $ld_cmd -i -T $combine_script -o $combined_obj_file $obj_files
if ($LASTEXITCODE -ne 0) { exit -1 }
# Export all C symbols to m2-compiled.asm, except for those in ext.c
"Reading symbols from $combined_obj_file..."
$combined_symbols = Get-Symbols $combined_obj_file
if ($LASTEXITCODE -ne 0) { exit -1 }
"Reading symbols from $undefine_obj_file..."
$ext_symbols = Get-Symbols $undefine_obj_file
if ($LASTEXITCODE -ne 0) { exit -1 }
"Exporting C symbols to $compiled_asm_file..."
$ext_symbols_names = $ext_symbols | Where-Object { $_.IsFunction -and $_.IsGlobal -and (-not $_.IsUndefined) } | ForEach-Object { $_.Name }
$exported_symbols = $combined_symbols | Where-Object { $_.IsFunction -and $_.IsGlobal -and (-not $_.IsUndefined) -and ($ext_symbols_names -notcontains $_.Name) }
$exported_symbols | Sort-Object -Property Name | ForEach-Object { ".definelabel $($_.Name),0x$($_.Value.ToString("X"))" } | Set-Content -Path $compiled_asm_file
# ------------------------ ASSEMBLE HACK CODE -----------------------
"Assembling $hack_asm_file..."
& $asm_cmd -root $src_dir -sym $hack_sym_file $hack_asm_file
if ($LASTEXITCODE -ne 0) { exit -1 }
# ------------------- GENERATE FINAL LINKER SCRIPT ------------------
"Writing $link_script..."
$hack_symbols = Get-SymfileSymbols "$([IO.Path]::Combine($src_dir, $hack_sym_file))"
$includes_symbols = Get-SymfileSymbols "$([IO.Path]::Combine($working_dir, $includes_sym_file))"
$asm_symbols = ($hack_symbols + $includes_symbols) | Where-Object { $_.IsGlobal }
Set-Content -Path $link_script -Value $link_script_contents
$asm_symbols | ForEach-Object { Add-Content -Path $link_script -Value "$($_.Name) = 0x$($_.Value.ToString("X"));" }
# ---------------------------- FINAL LINK ---------------------------
"Linking to $linked_obj_file..."
& $ld_cmd -T $link_script -o $linked_obj_file $combined_obj_file
if ($LASTEXITCODE -ne 0) { exit -1 }
# -------------------- COPY COMPILED C CODE TO ROM ------------------
"Copying compiled code to $output_rom_file..."
$sections = Get-SectionInfo $linked_obj_file
if ($LASTEXITCODE -ne 0) { exit -1 }
$text_section = $sections[".text"]
$linked_bytes = [IO.File]::ReadAllBytes($linked_obj_file)
$rom_bytes = [IO.File]::ReadAllBytes($output_rom_file)
[System.Array]::Copy($linked_bytes, $text_section.Offset, $rom_bytes, $text_section.Address - 0x8000000, $text_section.Size)
[IO.File]::WriteAllBytes($output_rom_file, $rom_bytes)
# -------------------------- GENERATE SYMBOLS -----------------------
"Generating $output_rom_sym_file..."
& dotnet $symbuilder_cmd $output_rom_sym_file "$([IO.Path]::Combine($src_dir, $hack_sym_file))" "$([IO.Path]::Combine($working_dir, $includes_sym_file))"
if ($LASTEXITCODE -ne 0) { exit -1 }
"Finished"
exit 0

View File

@ -1,22 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26403.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amalgamator", "Amalgamator\Amalgamator.csproj", "{24518872-E9D3-4C09-BE31-A2B52A6634F2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{24518872-E9D3-4C09-BE31-A2B52A6634F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{24518872-E9D3-4C09-BE31-A2B52A6634F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24518872-E9D3-4C09-BE31-A2B52A6634F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24518872-E9D3-4C09-BE31-A2B52A6634F2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{24518872-E9D3-4C09-BE31-A2B52A6634F2}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>Amalgamator</RootNamespace>
<AssemblyName>Amalgamator</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="ELFSharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=22f480d29a3ced83, processorArchitecture=MSIL">
<HintPath>..\packages\ELFSharp.1.0.4\lib\net40\ELFSharp.dll</HintPath>
</Reference>
<Reference Include="FluentCommandLineParser, Version=1.4.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\FluentCommandLineParser.1.4.3\lib\net35\FluentCommandLineParser.dll</HintPath>
</Reference>
<Reference Include="MiscUtil, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d3c42c4bfacf7596, processorArchitecture=MSIL">
<HintPath>..\packages\JonSkeet.MiscUtil.0.1\lib\net35-Client\MiscUtil.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
</Project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
</configuration>

View File

@ -1,331 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.IO;
using System.Diagnostics;
using Fclp;
using ELFSharp.ELF;
using ELFSharp.ELF.Sections;
namespace Amalgamator
{
class Program
{
const string GccExec = "arm-none-eabi-gcc";
const string LdExec = "arm-none-eabi-ld";
const string CompiledAsmFile = "m2-compiled.asm";
const string ArmipsExec = "armips.exe";
const string HackFile = "m2-hack.asm";
const string ArmipsSymFile = "armips-symbols.sym";
const string IncludesSymFile = "working\\includes-symbols.sym";
const string LinkerScript = "linker.ld";
const string LinkedObjectFile = "linked.o";
const string ArmipsSymbolRegex = @"([0-9a-fA-F]{8}) ([^\.@]\S+)";
static int Main(string[] args)
{
int exitCode = MainInternal(args);
return exitCode;
}
// I'm having Main wrap everything so that it's easier to break on error before the program returns
static int MainInternal(string[] args)
{
var options = GetOptions(args);
if (options == null)
return 1;
var functionSymbols = new Dictionary<string, uint>();
var undefinedSymbols = new HashSet<string>();
var linkerScript = new StringBuilder();
foreach (string codeFile in options.CodeFiles)
{
bool success = CompileCodeFile(codeFile);
if (!success)
{
Console.Error.WriteLine($"Error compiling {codeFile}");
return 2;
}
// Skip the dummy function stubs from ext.o
if (codeFile == "ext.c")
continue;
var elf = ELFReader.Load(GetObjectFileName(codeFile));
var symbols = elf.GetSection(".symtab") as SymbolTable<uint>;
foreach (var symbol in symbols.Entries.Where(
s => s.Type == SymbolType.Function && s.Binding.HasFlag(SymbolBinding.Global)))
{
functionSymbols.Add(symbol.Name, symbol.Value);
}
foreach (var symbol in symbols.Entries.Where(
s => s.Type == SymbolType.NotSpecified && s.Binding.HasFlag(SymbolBinding.Global) && s.PointedSectionIndex == 0))
{
undefinedSymbols.Add(symbol.Name);
}
}
GenerateCompiledLabelsFile(options.GetRootFile(CompiledAsmFile),
functionSymbols, options.CompiledAddress);
if (!RunAssembler(options.RootDirectory))
return 3;
linkerScript.AppendLine($"SECTIONS {{ .text 0x{options.CompiledAddress:X} : {{ *(.text .data .rodata) }} }}");
foreach (var sym in EnumerateArmipsSymbols(options.GetRootFile(ArmipsSymFile))
.Concat(EnumerateArmipsSymbols(options.GetRootFile(IncludesSymFile)))
.Where(s => undefinedSymbols.Contains(s.Key)))
{
linkerScript.AppendLine($"{sym.Key} = 0x{sym.Value:X};");
}
File.WriteAllText(LinkerScript, linkerScript.ToString());
if (!RunLinker(options.RootDirectory, options.CodeFiles.Select(c => GetObjectFileName(c))))
return 4;
byte[] code = GenerateCompiledBinfile();
if (code == null)
return 5;
RemoveUnwantedCodeDataSymbol(options.GetRootFile(ArmipsSymFile), options.CompiledAddress);
IncludeBinfile(options.GetRootFile(options.RomName), code, options.CompiledAddress);
return 0;
}
static Options GetOptions(string[] args)
{
var options = new Options();
var parser = new FluentCommandLineParser();
parser.Setup<string>('c', "compiled-address")
.Callback(i => options.CompiledAddress = (int)new System.ComponentModel.Int32Converter().ConvertFromString(i))
.Required();
parser.Setup<string>('d', "root-directory")
.Callback(s => options.RootDirectory = s);
parser.Setup<string>('r', "rom-file")
.Callback(s => options.RomName = s)
.Required();
parser.Setup<List<string>>('i', "input-files")
.Callback(s => options.CodeFiles = s)
.Required();
var result = parser.Parse(args);
if (result.HasErrors)
Console.WriteLine(result.ErrorText);
return result.HasErrors ? null : options;
}
static int ParsePossiblyHexNumber(string str)
{
return (int)new System.ComponentModel.Int32Converter().ConvertFromString(str);
}
static int RunLocalProcess(string fileName, params string[] args)
=> RunProcess(fileName, null, args);
static int RunProcess(string fileName, string workingDirectory, params string[] args)
=> RunProcess(fileName, Process_OutputDataReceived, Process_ErrorDataReceived, workingDirectory, args);
static int RunProcess(string fileName,
DataReceivedEventHandler outputCallback,
DataReceivedEventHandler errorCallback,
string workingDirectory,
params string[] args)
{
var process = new Process();
process.StartInfo.FileName = Path.Combine((workingDirectory ?? ""), fileName);
process.StartInfo.Arguments = String.Join(" ", args);
if (workingDirectory != null)
{
process.StartInfo.WorkingDirectory = Path.GetFullPath(workingDirectory);
}
Console.WriteLine($"Executing: {process.StartInfo.FileName} {process.StartInfo.Arguments}");
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
if (errorCallback != null)
process.ErrorDataReceived += errorCallback;
if (outputCallback != null)
process.OutputDataReceived += outputCallback;
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit();
return process.ExitCode;
}
static int RunProcess(string fileName, out string standardOutput, params string[] args)
{
var outputBuilder = new StringBuilder();
DataReceivedEventHandler outputCallback = (o, e) => outputBuilder.AppendLine(e.Data);
int exitCode = RunProcess(fileName, outputCallback, null, null, args);
standardOutput = outputBuilder.ToString();
return exitCode;
}
static string GetObjectFileName(string codeFileName)
{
return Path.GetFileNameWithoutExtension(codeFileName) + ".o";
}
static void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Data))
Console.Out.WriteLine(e.Data);
}
static void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Data))
Console.Error.WriteLine(e.Data);
}
static bool CompileCodeFile(string fileName)
{
string objectName = GetObjectFileName(fileName);
return RunLocalProcess(GccExec,
"-o", objectName,
"-c",
"-O3",
"-fno-ipa-cp",
"-fno-inline",
fileName,
"-march=armv4t",
"-mtune=arm7tdmi",
"-mthumb",
"-ffixed-r12",
"-mno-long-calls") == 0;
}
static IEnumerable<KeyValuePair<string, int>> ParseAddressSymbolMatches(IEnumerable<Match> matches)
{
var symbols = new Dictionary<string, int>();
foreach (var match in matches)
{
// There should be exactly three groups (one for the full match + 2 captured groups)
if (match.Groups.Count != 3)
continue;
string addressString = match.Groups[1].Value;
string symbolName = match.Groups[2].Value;
int address = int.Parse(addressString, System.Globalization.NumberStyles.HexNumber);
symbols.Add(symbolName, address);
}
return symbols;
}
static IEnumerable<KeyValuePair<string, int>> EnumerateArmipsSymbols(string armipsSymbolsFile)
{
string armipsSymbols = File.ReadAllText(armipsSymbolsFile);
var regex = new Regex(ArmipsSymbolRegex);
var matches = regex.Matches(armipsSymbols).Cast<Match>();
return ParseAddressSymbolMatches(matches);
}
static void GenerateCompiledLabelsFile(string fileName,
IEnumerable<KeyValuePair<string, uint>> functionSymbols, int compiledAddress)
{
using (var writer = File.CreateText(fileName))
{
// Need to clear the 1 bit because armips requires aligned label addresses
foreach (var kv in functionSymbols)
writer.WriteLine($".definelabel {kv.Key},0x{(kv.Value & ~1) + compiledAddress:X}");
}
}
static bool RunAssembler(string rootDirectory)
{
return RunProcess(ArmipsExec, rootDirectory, HackFile, "-sym", ArmipsSymFile) == 0;
}
static bool RunLinker(string rootDirectory, IEnumerable<string> objectFiles)
{
return RunLocalProcess(LdExec,
"-o", LinkedObjectFile,
String.Join(" ", objectFiles),
"-T", LinkerScript) == 0;
}
static byte[] GenerateCompiledBinfile()
{
var code = ExtractObjectSection(LinkedObjectFile, ".text");
return code;
}
static byte[] ExtractObjectSection(string objectFile, string sectionName)
{
var elf = ELFReader.Load(objectFile);
var section = elf.GetSection(sectionName);
return section.GetContents();
}
static void RemoveUnwantedCodeDataSymbol(string symbolFile, int compiledAddress)
{
var symbols = File.ReadAllLines(symbolFile).ToList();
int unwantedIndex = symbols.FindIndex(l =>
l.StartsWith(compiledAddress.ToString("X8")) &&
l.Substring(9, 5) == ".byt:");
if (unwantedIndex >= 0)
symbols.RemoveAt(unwantedIndex);
File.WriteAllLines(symbolFile, symbols.ToArray());
}
static void IncludeBinfile(string outputFile, byte[] data, int compiledAddress)
{
byte[] output = File.ReadAllBytes(outputFile);
Array.Copy(data, 0, output, compiledAddress & 0x1FFFFFF, data.Length);
File.WriteAllBytes(outputFile, output);
}
}
class Options
{
public int CompiledAddress { get; set; }
public string RootDirectory { get; set; }
public string RomName { get; set; }
public List<string> CodeFiles { get; set; }
public string GetRootFile(string fileName)
{
return Path.Combine(Path.GetFullPath(RootDirectory), fileName);
}
public string GetQuotedRootFile(string fileName)
{
return "\"" + GetRootFile(fileName) + "\"";
}
}
}

View File

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Amalgamator")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Amalgamator")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("24518872-e9d3-4c09-be31-a2b52a6634f2")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ELFSharp" version="1.0.4" targetFramework="net461" />
<package id="FluentCommandLineParser" version="1.4.3" targetFramework="net461" />
<package id="JonSkeet.MiscUtil" version="0.1" targetFramework="net461" />
</packages>

View File

@ -1,27 +0,0 @@
@echo off
echo Copying fresh ROM...
copy /Y m12fresh.gba m12.gba
:: Compile text files
echo Compiling text files...
pushd ScriptTool\ScriptTool\bin\Debug
ScriptTool.exe -compile -main -misc "..\..\..\..\working" "..\..\..\..\eb.smc" "..\..\..\..\m12fresh.gba"
popd
:: Assemble includes
echo Assembling includes...
pushd working
..\armips.exe m12-includes.asm -sym includes-symbols.sym
popd
:: Compile all C and ASM code
echo Compiling and assembling...
pushd compiled
Amalgamator\Amalgamator\bin\Debug\Amalgamator.exe -r m12.gba -c 0x8100000 -d "../" -i vwf.c ext.c
popd
if errorlevel 1 (pause && goto :eof)
SymbolTableBuilder\SymbolTableBuilder\bin\Debug\symbols.exe m12.sym armips-symbols.sym working/includes-symbols.sym
echo Success!

View File

@ -1,5 +1,5 @@
.gba
.open "m12.gba",0x8000000
.open "../bin/m12.gba",0x8000000
//==============================================================================
// Relocation hacks

View File

@ -1,8 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -1,12 +0,0 @@
using System;
namespace CompileTool
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

View File

@ -1,10 +1,8 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.168
# Visual Studio Version 16
VisualStudioVersion = 16.0.28407.52
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompileTool", "CompileTool\CompileTool.csproj", "{44399E43-DE61-41ED-85AE-5F3A9DA15970}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptTool", "ScriptTool\ScriptTool.csproj", "{29AB41A8-6405-4C4B-A374-BF1BD7AF1A27}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SymbolTableBuilder", "SymbolTableBuilder\SymbolTableBuilder.csproj", "{2E0E2F98-4EB7-48C7-968D-1BEDCADA37F2}"
@ -17,10 +15,6 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{44399E43-DE61-41ED-85AE-5F3A9DA15970}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44399E43-DE61-41ED-85AE-5F3A9DA15970}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44399E43-DE61-41ED-85AE-5F3A9DA15970}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44399E43-DE61-41ED-85AE-5F3A9DA15970}.Release|Any CPU.Build.0 = Release|Any CPU
{29AB41A8-6405-4C4B-A374-BF1BD7AF1A27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29AB41A8-6405-4C4B-A374-BF1BD7AF1A27}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29AB41A8-6405-4C4B-A374-BF1BD7AF1A27}.Release|Any CPU.ActiveCfg = Release|Any CPU

26
tools/ScriptTool/Asset.cs Normal file
View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace ScriptTool
{
internal static class Asset
{
public static readonly string AssetPath;
static Asset()
{
AssetPath = AppDomain.CurrentDomain.BaseDirectory;
}
private static string GetFullPath(string path)
=> Path.Combine(AssetPath, path);
public static string ReadAllText(string path)
=> File.ReadAllText(GetFullPath(path));
public static byte[] ReadAllBytes(string path)
=> File.ReadAllBytes(GetFullPath(path));
}
}

View File

@ -20,8 +20,8 @@ namespace ScriptTool
static Compiler()
{
byte[] widths = File.ReadAllBytes("m2-widths-main.bin");
byte[] saturnWidths = File.ReadAllBytes("m2-widths-saturn.bin");
byte[] widths = Asset.ReadAllBytes("m2-widths-main.bin");
byte[] saturnWidths = Asset.ReadAllBytes("m2-widths-saturn.bin");
virtualWidths = new int[widths.Length / 2];
renderWidths = new int[widths.Length / 2];
virtualWidthsSaturn = new int[saturnWidths.Length / 2];

View File

@ -36,7 +36,7 @@ namespace ScriptTool
// Load codes
Codes = JsonConvert.DeserializeObject<List<EbControlCode>>(
File.ReadAllText("eb-codelist.json"));
Asset.ReadAllText("eb-codelist.json"));
}
public bool IsMatch(byte[] rom, int address)

View File

@ -26,7 +26,7 @@ namespace ScriptTool
static M12ControlCode()
{
Codes = JsonConvert.DeserializeObject<List<M12ControlCode>>(
File.ReadAllText("m12-codelist.json"));
Asset.ReadAllText("m12-codelist.json"));
}
public bool IsMatch(byte[] rom, int address)

View File

@ -39,8 +39,8 @@ namespace ScriptTool
return -1;
}
m12CharLookup = JsonConvert.DeserializeObject<Dictionary<byte, string>>(File.ReadAllText("m12-char-lookup.json"));
ebCharLookup = JsonConvert.DeserializeObject<Dictionary<byte, string>>(File.ReadAllText("eb-char-lookup.json"));
m12CharLookup = JsonConvert.DeserializeObject<Dictionary<byte, string>>(Asset.ReadAllText("m12-char-lookup.json"));
ebCharLookup = JsonConvert.DeserializeObject<Dictionary<byte, string>>(Asset.ReadAllText("eb-char-lookup.json"));
if (options.Command == CommandType.Decompile)
{
@ -74,7 +74,7 @@ namespace ScriptTool
using (IncludeFile = File.CreateText(Path.Combine(options.WorkingDirectory, "m12-includes.asm")))
{
IncludeFile.WriteLine(".gba");
IncludeFile.WriteLine(".open \"../m12.gba\",0x8000000");
IncludeFile.WriteLine(".open \"../bin/m12.gba\",0x8000000");
// Compile main string tables
if (options.DoMainText)