Compare commits

...
Sign in to create a new pull request.

38 commits

Author SHA1 Message Date
ea64ac981c
Use val field of getopt option struct 2025-07-16 23:24:33 +01:00
38187b17a5
Fix bug where on some platforms (inc. Windows), duplicate IAT entries would be written 2025-07-02 00:24:18 +01:00
bbafc3d7ac
Re-add comments to some files 2025-06-30 18:07:26 +01:00
8bc36751c9
Bump version, update README 2025-06-30 16:11:26 +01:00
4d67f29fa3
Get imports *finally* working (remove special handling for functions and treat them as variables) 2025-06-30 15:16:20 +01:00
34b49c9a1d
Add module indexes to PE IAT entries 2025-06-30 03:21:23 +01:00
b8be6481e0
No, iatAddr doesn't contain a flag 2025-06-30 00:08:26 +01:00
9d36dd8ec9
Do more work on imports 2025-06-30 00:03:06 +01:00
3527e2e76d Remove tabs from Guile indentation 2025-06-26 01:12:45 +01:00
a518314dee Add Guix support 2025-06-25 23:51:54 +01:00
9992242f5b Correct version regex in CMakeLists.txt 2025-06-24 18:27:44 +01:00
f505302e71 Change the way versioning works, fix memory leaks 2025-06-24 18:20:43 +01:00
3c46c6cf74 Restore comments which were accidentally deleted during reformatting 2025-06-21 21:46:43 +01:00
f6610667ae Remove prints from everywhere but main, fix bug where only part of an import library version was set 2025-06-16 22:06:31 +01:00
b646f28812 Tweak formatting, add CONTRIBUTING.md 2025-06-16 21:31:17 +01:00
dba2f4c54c Change how getXXFromYY functions handle errors (utilise errno) 2025-06-16 17:12:27 +01:00
1ff1e8f93f Reformat all code to modified Allman/BSD style 2025-06-16 15:31:35 +01:00
Vali0004
4e053b09ca Refactor code style, and add hardening
Signed-off-by: Aiden Isik <aidenisik+srht@member.fsf.org>
2025-06-16 14:27:12 +01:00
Vali0004
1938c71369 Add Nix support
Signed-off-by: Aiden Isik <aidenisik+srht@member.fsf.org>
2025-06-16 14:19:02 +01:00
434ed63db9 Use UBSan as well 2025-06-15 22:29:47 +01:00
3d57c01b92 Do not use base address to determine DLL type, revert to old method 2025-06-15 12:36:09 +01:00
7b1d147cf0 Fix bug where if there are no import tables, the location of the first optional header overwrites the basefile 2025-06-10 16:52:07 +01:00
75e2a58ade Use PE DLL flag to decide whether to use DLL or Title flag (more accurate?) 2025-06-09 21:58:32 +01:00
1dab6189be Remove some debugging stuff 2025-06-09 20:17:57 +01:00
d82fd383f0 Rework executable type logic, add command line option to override it 2025-06-09 19:10:55 +01:00
32bdb84887 Fix unaligned pointer accesses and buffer overflow bug in freeImportLibrariesStruct 2025-06-09 14:57:06 +01:00
81b649ca32 Finish import table generation 2025-06-09 13:47:58 +01:00
1b62027120 Fix name table stuff 2025-06-08 19:01:22 +01:00
080df81ef6 Add code to populate XEX import libraries header (breaks things, need to fix) 2025-06-08 16:42:13 +01:00
aee6624e02 Rename getdata to gethdrdata 2025-06-03 18:43:37 +01:00
c0607ecc49 Improve error handling in main, other misc bug fixes 2025-06-03 18:24:07 +01:00
24c68d7ff1 Put better memory management routines in place (TODO: use them everywhere) 2025-06-03 01:27:05 +01:00
0005775f85 Add code for locating branch stubs for imported functions 2025-06-02 20:26:36 +01:00
4041036c65 Fix what currently exists of PE import reading 2025-06-01 23:23:46 +01:00
f3e972a99f Bump version 2025-06-01 19:29:49 +01:00
8ff9bb7582 Start working on import data parsing from PE (untested) 2025-06-01 19:14:14 +01:00
6c5274ea54 Set up data structures/helper functions for PE import parsing, make byte-swapping read functions more efficient 2025-06-01 15:49:47 +01:00
868e78d81b Remove redundant (and POSIX/MinGW-only) file size check 2025-05-30 18:35:13 +01:00
28 changed files with 2699 additions and 1165 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
build
result
*~
*#
\#*
*.orig

View file

@ -16,79 +16,63 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# This version of CMake is available on Debian bullseye backports. I think that's a reasonably old version that most people should have.
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.24.2)
project(synthxex)
# Make sure we're not being compiled with MSVC (we don't support it)
if(MSVC)
message(SEND_ERROR "Compiling with MSVC is not supported. Please use GCC or Clang via the -DCMAKE_C_COMPILER flag or CC environment variable. (Example: cmake -DCMAKE_C_COMPILER=\"gcc\" ..).")
return()
endif()
# Setting sources...
set(allsources ${CMAKE_SOURCE_DIR}/src/main.c)
# Gather all source and header files
file(GLOB_RECURSE allsources
${CMAKE_SOURCE_DIR}/src/*.c
${CMAKE_SOURCE_DIR}/src/*.h
${CMAKE_SOURCE_DIR}/include/getopt_port/*.c
${CMAKE_SOURCE_DIR}/include/getopt_port/*.h
${CMAKE_SOURCE_DIR}/include/nettle/*.c
${CMAKE_SOURCE_DIR}/include/nettle/*.h
)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/common/common.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/common/crypto.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/common/datastorage.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/common/datastorage.c)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/getdata/getdata.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/getdata/getdata.c)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/pemapper/pemapper.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/pemapper/pemapper.c)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/placer/placer.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/placer/placer.c)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/setdata/optheaders.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/setdata/pagedescriptors.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/setdata/populateheaders.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/setdata/optheaders.c)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/setdata/pagedescriptors.c)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/setdata/populateheaders.c)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/write/headerhash.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/write/writexex.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/write/headerhash.c)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/src/write/writexex.c)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/include/getopt_port/getopt.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/include/getopt_port/getopt.c)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/include/nettle/macros.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/include/nettle/nettle-types.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/include/nettle/nettle-write.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/include/nettle/sha1.h)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/include/nettle/sha1.c)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/include/nettle/sha1-compress.c)
list(APPEND allsources ${CMAKE_SOURCE_DIR}/include/nettle/write-be32.c)
# Setting compilation settings...
# Setting compilation settings
add_executable(synthxex ${allsources})
target_include_directories(synthxex PRIVATE ${CMAKE_SOURCE_DIR}/include)
# If we're doing a debug build, compile with debugging and git commit hash
if(NOT ("${CMAKE_BUILD_TYPE}" STREQUAL "Release"))
# Debug/release build handling
if(GENERATOR_IS_MULTI_CONFIG)
set(SYNTHXEX_BUILD_TYPE "MultiConf") # Multi-config generators handle debug build options themselves, don't apply ours
else()
if(CMAKE_BUILD_TYPE)
set(SYNTHXEX_BUILD_TYPE ${CMAKE_BUILD_TYPE})
else()
set(SYNTHXEX_BUILD_TYPE "Debug")
endif()
endif()
# MinGW doesn't support ASAN
if(NOT (MINGW))
target_compile_options(synthxex PRIVATE -O0 -g -fsanitize=address)
target_link_options(synthxex PRIVATE -fsanitize=address)
if(${SYNTHXEX_BUILD_TYPE} MATCHES "Deb")
add_compile_definitions(_DEBUG=1)
if(NOT MINGW)
target_compile_options(synthxex PRIVATE -O0 -g -fsanitize=address -fsanitize=undefined)
target_link_options(synthxex PRIVATE -lasan -lubsan -fsanitize=address -fsanitize=undefined)
else()
target_compile_options(synthxex PRIVATE -O0 -g)
endif()
endif()
# Set the version
execute_process(
COMMAND git rev-parse --short HEAD
COMMAND git describe --tags --dirty
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT
OUTPUT_VARIABLE VERSION_STRING
OUTPUT_STRIP_TRAILING_WHITESPACE
)
add_compile_definitions(GIT_COMMIT="${GIT_COMMIT}")
# Only use the result from the git command if it's a valid version string
if(${VERSION_STRING} MATCHES "^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+-g[0-9a-f]+(-dirty)?)?$")
add_compile_definitions(SYNTHXEX_VERSION="${VERSION_STRING}")
else()
add_compile_definitions(SYNTHXEX_VERSION="v0.0.5") # Only used as a fallback
endif()
# Setting install target settings...

124
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,124 @@
# Contributing to SynthXEX
## Bug reports
Please direct any bug reports to either the [main repository](https://git.aidenisik.scot/FreeChainXenon/SynthXEX/issues), or the [GitHub mirror](https://github.com/FreeChainXenon/SynthXEX/issues).
## Patches
To contribute code, first ensure you have read and adhered to the [code style](#code-style) section.
There are two ways to contribute code patches:
- Pull requests, which you can submit either to the [main repository](https://git.aidenisik.scot/FreeChainXenon/SynthXEX/pulls), or the [GitHub mirror](https://github.com/FreeChainXenon/SynthXEX/pulls).
- Patch files, generated by Git, can be sent to \<aidenisik+git@member.fsf.org\>, which I will review and possibly merge.
## Code Style
If you are using [AStyle](https://astyle.sourceforge.net/), the following command will format the code correctly (run from the project root):
```
astyle --style=allman --add-braces --break-blocks --indent-labels --indent-switches --unpad-paren --align-pointer=name --pad-oper --indent-preproc-block --add-one-line-braces --squeeze-ws --squeeze-lines=1 --recursive "./src/*.c" "./src/*.h"
```
The code style used in SynthXEX is the [Allman/BSD style as specified by AStyle](https://astyle.sourceforge.net/astyle.html#_style=allman), with the following extra specifications:
- C++-style comments are used, rather than multiline/C89-style comments, e.g:
```
// This is a comment
// This is another comment
// This is yet another comment
```
- All conditional statements utilise curly brackets, even when there is only one line within them, e.g:
```
if(condition)
{
printf("Hello!\n");
}
```
- It is acceptable to have the curly brackets on the same line as the code if it is short, e.g:
```
if(condition) { printf("Hello!\n"); }
if(condition)
{ printf("Hello!\n"); }
```
- All brackets immediately succeed/precede the name/keyword they correspond to with no spaces, e.g:
```
foo(bar);
(float)(a + b);
```
- All code blocks have one empty line between the start/end and any other code, e.g.:
```
int a = 1;
if(a == 2)
{
printf("Hi!\n");
}
int b = 2;
```
- Labels are always 1 indentation level below the current level, e.g:
```
if(1)
{
if(2)
{
int a = 1;
label1:
int b = 2;
}
}
```
- Case statements are always 1 indentation level above their corresponding switch, e.g.:
```
switch(val)
{
case 1:
printf("1\n");
break;
default:
printf("0\n");
break;
}
```
- Pointer operators are always attached to variables, e.g:
```
char *string = NULL;
int a = 0;
setA(&a);
```
- Operators are always padded to be one space away from their operands, e.g:
```
int a = 1 + 2;
```
- Commas in function calls, etc, always have one space after them, e.g:
```
myFunc(1, 2);
```
- Preprocessor macros outside functions are indented, just like regular code, e.g:
```
#ifdef MACRO_ONE
#define MACRO 1
#else
#define MACRO 0
#endif
```
- There should never be more than one blank line between code, e.g:
```
int a;
int b;
int c;
```

106
README.md
View file

@ -10,20 +10,17 @@ SynthXEX is the XEX(2) builder (and thus the last step in building an executable
This was developed by referencing public knowledge on the XEX(2) file format, along with staring at XEX files in a hex editor to decypher aspects which do not seem to be documented anywhere. No Microsoft code was decompiled to develop SynthXEX, and I ask that any contributors do not do so either.
This is in early development and MANY features are missing (most notable imports/exports). No guarantees are made as to the functionality of the program, or it's stability. Functionality may change at any time.
This is in early development and MANY features are missing (most notable exports). No guarantees are made as to the functionality of the program, or it's stability. Functionality may change at any time.
This *should* support any POSIX-compliant operating system, along with Windows.
## Installing
SynthXEX is part of the FreeChainXenon toolchain. It's installer is located [here](https://git.aidenisik.scot/FreeChainXenon/FCX-Installer).
## Building
## Building (POSIX, standard method)
### Prerequisites
- A C compiler (GCC/Clang)
- CMake (>= 3.25.1)
- CMake (>= 3.24.2)
- Make
- Git
@ -31,7 +28,7 @@ To install these on Debian: ```sudo apt install gcc cmake make git```
### Downloading
Run: ```git clone https://git.aidenisik.scot/FreeChainXenon/SynthXEX```
Clone: ```git clone https://git.aidenisik.scot/FreeChainXenon/SynthXEX```
### Compiling
@ -45,15 +42,102 @@ Build: ```make```
Install: ```sudo make install```
## Building (Guix)
### Prerequisites
- Guix
- Git
### Compiling
Choose ONE of the options below.
#### Compiling & Installing
Clone: ```git clone https://git.aidenisik.scot/FreeChainXenon/SynthXEX```
Switch to the newly cloned directory: ```cd SynthXEX```
Install: ```guix time-machine -C channels.scm -- package -f synthxex.scm```
#### Compiling Only
Clone: ```git clone https://git.aidenisik.scot/FreeChainXenon/SynthXEX```
Switch to the newly cloned directory: ```cd SynthXEX```
Build: ```guix time-machine -C channels.scm -- build -f synthxex.scm```
#### Compiling & Installing to Config
TODO
## Building (Nix)
### Prerequisites
- Nix
- Git
### Compiling
Choose ONE of the options below.
#### Compiling & Installing
Clone: ```git clone https://git.aidenisik.scot/FreeChainXenon/SynthXEX```
Switch to the newly cloned directory: ```cd SynthXEX```
Run: ```nix profile install .#synthxex```
#### Compiling Only
Clone: ```git clone https://git.aidenisik.scot/FreeChainXenon/SynthXEX```
Switch to the newly cloned directory: ```cd SynthXEX```
Run: ```nix build .#synthxex```
#### Compiling & Installing to Config
Add SynthXEX to your configuration.nix file:
```
{ config, modulesPath, lib, pkgs, ... }:
let
synthxex = builtins.getFlake "git+https://git.aidenisik.scot/FreeChainXenon/SynthXEX";
synthxexPkgs = synthxex.packages.${pkgs.hostPlatform.system};
in {
# ...
environment.systemPackages = with pkgs; [
# ...
synthxexPkgs.synthxex
];
}
```
## Special thanks to:
- [InvoxiPlayGames](https://github.com/InvoxiPlayGames), for help understanding the XEX format
- [emoose](https://github.com/emoose), for xex1tool, which was very useful for taking apart XEX files and getting info from them
- [Free60Project](https://github.com/Free60Project), for documentation on the XEX format, which was useful in the early days
- Several other members of the Xbox360Hub Discord server, for a multitude of reasons
- [Vali0004](https://github.com/Vali0004), for helping clean up and harden code, along with Nix packaging
- Several members of the Xbox360Hub Discord server, for a multitude of reasons
## Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md).
## Licensing
### SynthXEX (src/*, CMakeLists.txt)
### SynthXEX
Copyright (c) 2024-25 Aiden Isik
@ -70,7 +154,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
### getopt_port (include/getopt_port/*)
### getopt_port
Copyright (c) 2012-2023, Kim Grasman <kim.grasman@gmail.com>
@ -98,7 +182,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
### GNU Nettle (include/nettle/*)
### GNU Nettle
Copyright (C) 2001, 2013 Niels Möller

14
channels.scm Normal file
View file

@ -0,0 +1,14 @@
;;; Lock the Guix repository to a specific commit.
;;; For reproducibility purposes in building SynthXEX.
(list (channel
(name 'guix)
(url "https://codeberg.org/guix/guix.git")
(branch "master")
(commit
"95f8c22bbe9cf25ce492ef520f94a34f7ec3d160")
(introduction
(make-channel-introduction
"9edb3f66fd807b096b48283debdcddccfea34bad"
(openpgp-fingerprint
"BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA")))))

60
flake.lock generated Normal file
View file

@ -0,0 +1,60 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1750032973,
"narHash": "sha256-UqiBHZueCKKB8oQrVFooBA0DHh8AUVwhqiLSdhSXyYA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "5a0be37d950a180a4853647674c89ec286e538c1",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

35
flake.nix Normal file
View file

@ -0,0 +1,35 @@
{
inputs = {
utils.url = "github:numtide/flake-utils";
# to update: nix flake update nixpkgs
nixpkgs.url = "github:nixos/nixpkgs";
};
outputs = { self, utils, nixpkgs }:
(utils.lib.eachSystem [ "x86_64-linux" "ppc64" "ppc32" ] (system:
let
pkgsLut = {
x86_64-linux = nixpkgs.legacyPackages.${system}.extend self.overlay;
ppc32 = import nixpkgs {
crossSystem.config = "powerpc-none-eabi";
system = "x86_64-linux";
overlays = [ self.overlay ];
};
ppc64 = import nixpkgs {
crossSystem.config = "powerpc64-unknown-linux-gnuabielfv2";
system = "x86_64-linux";
overlays = [ self.overlay ];
config.allowUnsupportedSystem = true;
};
};
pkgs = pkgsLut.${system};
in {
packages = {
inherit (pkgs) synthxex;
};
devShell = pkgs.synthxex;
})) // {
overlay = self: super: {
synthxex = self.callPackage ./synthxex.nix {};
};
};
}

View file

@ -19,24 +19,28 @@
#pragma once
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
// Program identifiers
#define NAME "SynthXEX"
#define VERSION "v0.0.3"
#define COPYRIGHT "2024-25"
// Forbid the use of the free function
#define freeOnlyUseThisFunctionInTheNullAndFreeFunctionNowhereElse free
#pragma GCC poison free
#ifdef GIT_COMMIT
#define VERSION_STRING NAME " " VERSION "-dev-" GIT_COMMIT
// Program identifiers (version set in CMakeLists.txt)
#if defined(DEBUG) || defined(_DEBUG)
#define SYNTHXEX_NAME "SynthXEX-Debug"
#else
#define VERSION_STRING NAME " " VERSION
#define SYNTHXEX_NAME "SynthXEX"
#endif
#define SYNTHXEX_COPYRIGHT "2024-2025"
#define SYNTHXEX_VERSION_STRING SYNTHXEX_NAME " " SYNTHXEX_VERSION
// Print constants
#define PRINT_STEM "SynthXEX>"
#define SYNTHXEX_PRINT_STEM SYNTHXEX_NAME ">"
// Return values
#define SUCCESS 0
@ -45,7 +49,9 @@
#define ERR_MISSING_SECTION_FLAG -2
#define ERR_FILE_OPEN -3
#define ERR_FILE_READ -4
#define ERR_OUT_OF_MEM -5
#define ERR_UNSUPPORTED_STRUCTURE -6
void infoPrint(char *str);
#define ERR_FILE_WRITE -5
#define ERR_OUT_OF_MEM -6
#define ERR_UNSUPPORTED_STRUCTURE -7
#define ERR_INVALID_RVA_OR_OFFSET -8
#define ERR_INVALID_IMPORT_NAME -9
#define ERR_DATA_OVERFLOW -10

View file

@ -18,6 +18,148 @@
#include "datastorage.h"
// Frees memory and sets the pointer to NULL
// If the pointer is currently NULL, do nothing (avoid double-free)
// Disable pointer type checking here to make this easier to use
void nullAndFree(void **ptr)
{
if(ptr != NULL)
{
if(*ptr != NULL)
{
freeOnlyUseThisFunctionInTheNullAndFreeFunctionNowhereElse(*ptr);
*ptr = NULL;
}
}
}
// These functions together handle freeing all of the main structs.
// They can also be called individually, and it doesn't matter if you call them twice.
// Each function checks if the struct going to be accessed is NULL, so it doesn't try
// to access unallocated data.
//
// DO NOT CALL THESE OUTWITH MAIN. Those which take double pointers will replace
// the pointer given to them with null ONLY in the function they're called in, not it's callers
// (including main), which WILL result in a double free.
//
// Even if it doesn't access data right now, it's still there so it's not forgotten
// about if it becomes necessary with future additions.
void freeOffsetsStruct(struct offsets **offsets)
{
if(offsets != NULL)
{
if(*offsets != NULL)
{
nullAndFree((void **) & ((*offsets)->optHeaders));
nullAndFree((void **)offsets);
}
}
}
void freeXexHeaderStruct(struct xexHeader **xexHeader)
{
if(xexHeader != NULL)
{
if(*xexHeader != NULL)
{ nullAndFree((void **)xexHeader); }
}
}
void freeSecInfoHeaderStruct(struct secInfoHeader **secInfoHeader)
{
if(secInfoHeader != NULL)
{
if(*secInfoHeader != NULL)
{
struct pageDescriptor *descriptors = (*secInfoHeader)->descriptors; // To avoid dereferencing an unaligned pointer
nullAndFree((void **)&descriptors);
//(*secInfoHeader)->descriptors = descriptors; // Not required in this case as secInfoHeader is freed anyways
nullAndFree((void **)secInfoHeader);
}
}
}
void freeSectionsStruct(struct sections *sections)
{
nullAndFree((void **) & (sections->section));
}
void freePeImportInfoStruct(struct peImportInfo *peImportInfo)
{
if(peImportInfo->tables != NULL)
{
// Free the imports within each table first, then the tables,
// otherwise we'll have a memory leak
for(uint32_t i = 0; i < peImportInfo->tableCount; i++)
{
nullAndFree((void **) & (peImportInfo->tables[i].name));
nullAndFree((void **) & (peImportInfo->tables[i].imports));
}
nullAndFree((void **) & (peImportInfo->tables));
}
}
void freePeDataStruct(struct peData **peData)
{
if(*peData != NULL)
{
freeSectionsStruct(&((*peData)->sections));
freePeImportInfoStruct(&((*peData)->peImportInfo));
nullAndFree((void **)peData);
}
}
void freeOptHeaderEntriesStruct(struct optHeaderEntries **optHeaderEntries)
{
if(*optHeaderEntries != NULL)
{
nullAndFree((void **) & ((*optHeaderEntries)->optHeaderEntry));
nullAndFree((void **)optHeaderEntries);
}
}
void freeImportLibrariesStruct(struct importLibraries *importLibraries)
{
struct importTable *importTables = importLibraries->importTables;
char *nameTable = importLibraries->nameTable; // Use these to avoid dereferencing unaligned pointers
nullAndFree((void **)&nameTable);
if(importTables != NULL)
{
for(uint32_t i = 0; i < importLibraries->tableCount; i++)
{
uint32_t *addresses = importTables[i].addresses; // Avoid dereferencing unaligned pointer
nullAndFree((void **)&addresses);
importTables[i].addresses = addresses;
}
nullAndFree((void **)&importTables);
importLibraries->importTables = importTables;
}
}
void freeOptHeadersStruct(struct optHeaders **optHeaders)
{
if(*optHeaders != NULL)
{
freeImportLibrariesStruct(&((*optHeaders)->importLibraries));
nullAndFree((void **)optHeaders);
}
}
void freeAllMainStructs(struct offsets **offsets, struct xexHeader **xexHeader, struct secInfoHeader **secInfoHeader,
struct peData **peData, struct optHeaderEntries **optHeaderEntries, struct optHeaders **optHeaders)
{
freeOffsetsStruct(offsets);
freeXexHeaderStruct(xexHeader);
freeSecInfoHeaderStruct(secInfoHeader);
freePeDataStruct(peData);
freeOptHeaderEntriesStruct(optHeaderEntries);
freeOptHeadersStruct(optHeaders);
}
uint32_t getNextAligned(uint32_t offset, uint32_t alignment)
{
if(offset % alignment) // If offset not aligned
@ -28,84 +170,108 @@ uint32_t getNextAligned(uint32_t offset, uint32_t alignment)
return offset; // Offset already aligned
}
// TODO: Combine all of these into a single function
uint32_t rvaToOffset(uint32_t rva, struct sections *sections)
{
if((sections->count > 0) && (rva >= sections->section[sections->count - 1].rva + sections->section[sections->count - 1].virtualSize))
{
return 0; // Not found (beyond end of PE)
}
for(int32_t i = sections->count - 1; i >= 0; i--)
{
if(rva >= sections->section[i].rva)
{ return (rva - sections->section[i].rva) + sections->section[i].offset; }
}
return 0; // Not found
}
uint32_t offsetToRVA(uint32_t offset, struct sections *sections)
{
if((sections->count > 0)
&& (offset >= sections->section[sections->count - 1].offset + sections->section[sections->count - 1].rawSize))
{
return 0; // Not found (beyond end of PE)
}
for(int32_t i = sections->count - 1; i >= 0; i--)
{
if(offset >= sections->section[i].offset)
{ return (offset - sections->section[i].offset) + sections->section[i].rva; }
}
return 0; // Not found
}
uint32_t get32BitFromPE(FILE *pe)
{
uint8_t store[4];
fread(store, sizeof(uint8_t), 4, pe);
uint32_t result;
errno = SUCCESS;
uint32_t result = 0;
for(int i = 0; i < 4; i++)
if(fread(&result, sizeof(uint32_t), 1, pe) != 1)
{
result |= store[i] << i * 8;
errno = ERR_FILE_READ;
return 0;
}
// If system is big endian, swap endianness (PE is LE)
#ifdef BIG_ENDIAN_SYSTEM
result = __builtin_bswap32(result);
#endif
return __builtin_bswap32(result);
#else
return result;
#endif
}
uint16_t get16BitFromPE(FILE *pe)
{
uint8_t store[2];
fread(store, sizeof(uint8_t), 2, pe);
uint16_t result;
errno = SUCCESS;
uint16_t result = 0;
for(int i = 0; i < 2; i++)
if(fread(&result, sizeof(uint16_t), 1, pe) != 1)
{
result |= store[i] << i * 8;
errno = ERR_FILE_READ;
return 0;
}
// If system is big endian, swap endianness (PE is LE)
#ifdef BIG_ENDIAN_SYSTEM
result = htons(result);
#endif
return __builtin_bswap16(result);
#else
return result;
#endif
}
uint32_t get32BitFromXEX(FILE *xex)
{
uint8_t store[4];
fread(store, sizeof(uint8_t), 4, xex);
uint32_t result;
errno = SUCCESS;
uint32_t result = 0;
for(int i = 0; i < 4; i++)
if(fread(&result, sizeof(uint32_t), 1, xex) != 1)
{
result |= store[i] << i * 8;
errno = ERR_FILE_READ;
return 0;
}
// If system and file endianness don't match we need to change it
#ifdef LITTLE_ENDIAN_SYSTEM
result = __builtin_bswap32(result);
#endif
return __builtin_bswap32(result);
#else
return result;
#endif
}
uint16_t get16BitFromXEX(FILE *xex)
{
uint8_t store[2];
fread(store, sizeof(uint8_t), 2, xex);
uint16_t result;
errno = SUCCESS;
uint32_t result = 0;
for(int i = 0; i < 2; i++)
if(fread(&result, sizeof(uint16_t), 1, xex) != 1)
{
result |= store[i] << i * 8;
errno = ERR_FILE_READ;
return 0;
}
// If system and file endianness don't match we need to change it
#ifdef LITTLE_ENDIAN_SYSTEM
result = __builtin_bswap16(result);
#endif
return __builtin_bswap16(result);
#else
return result;
#endif
}

View file

@ -67,6 +67,9 @@
#define PE_SECTION_FLAG_READ 0x40000000
#define PE_SECTION_FLAG_WRITE 0x80000000
// PE import ordinal flag
#define PE_IMPORT_ORDINAL_FLAG 0x80000000
// RWX flags (XEX)
#define XEX_SECTION_CODE 0x1
#define XEX_SECTION_RWDATA 0x2
@ -76,18 +79,37 @@
struct sections
{
uint16_t count;
struct sectionPerms *sectionPerms;
struct section *section;
};
struct sectionPerms
struct section
{
uint8_t permFlag;
uint32_t virtualSize;
uint32_t rva;
uint32_t rawSize;
uint32_t offset;
};
struct peImport
{
uint32_t iatAddr;
};
struct peImportTable
{
char *name;
uint32_t rva;
uint32_t importCount;
struct peImport *imports;
};
struct peImportInfo
{
uint16_t count;
uint32_t idtRVA;
uint32_t tableCount;
uint32_t totalImportCount;
struct peImportTable *tables;
};
struct peExportInfo
@ -173,10 +195,26 @@ struct __attribute__((packed)) basefileFormat
uint32_t zeroSize;
};
struct __attribute__((packed)) importTable
{
uint32_t size;
uint8_t sha1[0x14];
uint32_t unknown; // Unique to each executable imported from, and always the same
uint32_t targetVer;
uint32_t minimumVer;
uint8_t padding;
uint8_t tableIndex;
uint16_t addressCount;
uint32_t *addresses; // IAT entry address followed by branch stub code address for functions, just IAT address for other symbols
};
struct __attribute__((packed)) importLibraries
{
uint32_t size;
uint8_t *data;
uint32_t nameTableSize;
uint32_t tableCount;
char *nameTable;
struct importTable *importTables;
};
struct __attribute__((packed)) tlsInfo
@ -206,11 +244,35 @@ struct optHeaders
struct tlsInfo tlsInfo;
};
// Functions used for easier memory management
// Frees memory and sets the pointer to NULL
// If the pointer is currently NULL, do nothing (avoid double-free)
void nullAndFree(void **ptr);
// These functions together handle freeing all of the main structs.
// They can also be called individually, and it doesn't matter if you call them twice.
// DO NOT CALL THESE OUTWITH MAIN. Those which take double pointers will replace
// the pointer given to them with null ONLY in the function they're called in, not it's callers
// (including main), which WILL result in a double free.
void freeOffsetsStruct(struct offsets **offsets);
void freeXexHeaderStruct(struct xexHeader **xexHeader);
void freeSecInfoHeaderStruct(struct secInfoHeader **secInfoHeader);
void freeSectionsStruct(struct sections *sections);
void freePeImportInfoStruct(struct peImportInfo *peImportInfo);
void freePeDataStruct(struct peData **peData);
void freeOptHeaderEntriesStruct(struct optHeaderEntries **optHeaderEntries);
void freeOptHeadersStruct(struct optHeaders **optHeaders);
void freeAllMainStructs(struct offsets **offsets, struct xexHeader **xexHeader, struct secInfoHeader **secInfoHeader,
struct peData **peData, struct optHeaderEntries **optHeaderEntries, struct optHeaders **optHeaders);
// Functions used for file data manipulation
uint32_t getNextAligned(uint32_t offset, uint32_t alignment);
// TODO: combine these into a single function
uint32_t rvaToOffset(uint32_t rva, struct sections *sections);
uint32_t offsetToRVA(uint32_t offset, struct sections *sections);
uint32_t get32BitFromPE(FILE *pe);
uint16_t get16BitFromPE(FILE *pe);
uint32_t get32BitFromXEX(FILE *xex);

View file

@ -1,232 +0,0 @@
// This file is part of SynthXEX, one component of the
// FreeChainXenon development toolchain
//
// Copyright (c) 2024-25 Aiden Isik
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "getdata.h"
// Validate PE. This isn't thorough, but it's enough to catch any non-PE/360 files.
// I was considering merging this with getHdrData and mapPEToBasefile as we're
// basically reading the same data twice, but I think it's beneficial to have a
// dedicated place where we validate the input.
bool validatePE(FILE *pe, bool skipMachineCheck) // True if valid, else false
{
// Check if we have at least the size of a DOS header, so we don't overrun the PE
fseek(pe, 0, SEEK_END);
size_t finalOffset = ftell(pe);
if(finalOffset < 0x3C + 0x4)
{
return false;
}
// Check magic
fseek(pe, 0, SEEK_SET);
uint16_t magic = get16BitFromPE(pe);
if(magic != 0x5A4D) // PE magic
{
return false;
}
// Check if pointer to PE header is valid
fseek(pe, 0x3C, SEEK_SET);
size_t peHeaderOffset = get32BitFromPE(pe);
if(finalOffset < peHeaderOffset)
{
return false;
}
// Check if the file is big enough to get size of optional header, and therefore size of whole PE header
if(finalOffset < 0x14 + 0x2)
{
return false;
}
// Check section count
fseek(pe, peHeaderOffset + 0x6, SEEK_SET);
uint16_t sectionCount = get16BitFromPE(pe);
if(sectionCount == 0)
{
return false;
}
// Check if the file is large enough to contain the whole PE header
fseek(pe, peHeaderOffset + 0x14, SEEK_SET);
uint16_t sizeOfOptHdr = get16BitFromPE(pe);
// 0x18 == size of COFF header, 0x28 == size of one entry in section table
if(finalOffset < peHeaderOffset + 0x18 + sizeOfOptHdr + (sectionCount * 0x28))
{
return false;
}
// Check machine ID
fseek(pe, peHeaderOffset + 0x4, SEEK_SET);
uint16_t machineID = get16BitFromPE(pe);
if(machineID != 0x1F2 && !skipMachineCheck) // 0x1F2 == POWERPCBE
{
return false;
}
// Check subsystem
fseek(pe, peHeaderOffset + 0x5C, SEEK_SET);
uint16_t subsystem = get16BitFromPE(pe);
if(subsystem != 0xE) // 0xE == XBOX
{
return false;
}
// Check page size/alignment
fseek(pe, peHeaderOffset + 0x38, SEEK_SET);
uint32_t pageSize = get32BitFromPE(pe);
if(pageSize != 0x1000 && pageSize != 0x10000) // 4KiB and 64KiB are the only valid sizes
{
return false;
}
// Check each raw offset + raw size in section table
fseek(pe, peHeaderOffset + 0x18 + sizeOfOptHdr + 0x10, SEEK_SET); // 0x10 == raw offset in entry
for(uint16_t i = 0; i < sectionCount; i++)
{
// If raw size + raw offset exceeds file size, PE is invalid
if(finalOffset < get32BitFromPE(pe) + get32BitFromPE(pe))
{
return false;
}
fseek(pe, 0x20, SEEK_CUR); // Next entry
}
return true; // Checked enough, this is an Xbox 360 PE file
}
int getSectionRwxFlags(FILE *pe, struct sections *sections)
{
fseek(pe, 0x3C, SEEK_SET);
uint32_t peOffset = get32BitFromPE(pe);
fseek(pe, peOffset + 0x6, SEEK_SET); // 0x6 == section count
sections->count = get16BitFromPE(pe);
sections->sectionPerms = calloc(sections->count, sizeof(struct sectionPerms)); // free() is called for this in setdata
if(sections->sectionPerms == NULL) {return ERR_OUT_OF_MEM;}
fseek(pe, peOffset + 0xF8, SEEK_SET); // 0xF8 == beginning of section table
for(uint16_t i = 0; i < sections->count; i++)
{
fseek(pe, 0xC, SEEK_CUR); // Seek to RVA of section
sections->sectionPerms[i].rva = get32BitFromPE(pe);
fseek(pe, 0x14, SEEK_CUR); // Now progress to characteristics, where we will check flags
uint32_t characteristics = get32BitFromPE(pe);
if(characteristics & PE_SECTION_FLAG_EXECUTE)
{
sections->sectionPerms[i].permFlag = XEX_SECTION_CODE | 0b10000; // | 0b(1)0000 == include size of 1
}
else if(characteristics & PE_SECTION_FLAG_WRITE || characteristics & PE_SECTION_FLAG_DISCARDABLE)
{
sections->sectionPerms[i].permFlag = XEX_SECTION_RWDATA | 0b10000;
}
else if(characteristics & PE_SECTION_FLAG_READ)
{
sections->sectionPerms[i].permFlag = XEX_SECTION_RODATA | 0b10000;
}
else
{
return ERR_MISSING_SECTION_FLAG;
}
// Don't need to progress any more to get to beginning of next entry, as characteristics is last field
}
return SUCCESS;
}
int getHdrData(FILE *pe, struct peData *peData, uint8_t flags)
{
// No flags supported at this time (will be used for getting additional info, for e.g. other optional headers)
if(flags)
{
return ERR_UNKNOWN_DATA_REQUEST;
}
// Get header data required for ANY XEX
// PE size
fseek(pe, 0, SEEK_SET); // If we don't do this, the size returned is wrong (?)
struct stat peStat;
fstat(fileno(pe), &peStat);
peData->size = peStat.st_size;
// Getting PE header offset before we go any further..
fseek(pe, 0x3C, SEEK_SET);
peData->peHeaderOffset = get32BitFromPE(pe);
// Number of sections
fseek(pe, peData->peHeaderOffset + 0x6, SEEK_SET);
peData->numberOfSections = get16BitFromPE(pe);
// Size of section table
peData->sectionTableSize = peData->numberOfSections * 0x28;
// Size of header
// 0x18 == size of COFF header, get16BitFromPE value == size of optional header
fseek(pe, peData->peHeaderOffset + 0x14, SEEK_SET);
peData->headerSize = (peData->peHeaderOffset + 1) + 0x18 + get16BitFromPE(pe);
// PE characteristics
fseek(pe, peData->peHeaderOffset + 0x16, SEEK_SET);
peData->characteristics = get16BitFromPE(pe);
// Entry point (RVA)
fseek(pe, peData->peHeaderOffset + 0x28, SEEK_SET);
peData->entryPoint = get32BitFromPE(pe);
// Base address
fseek(pe, peData->peHeaderOffset + 0x34, SEEK_SET);
peData->baseAddr = get32BitFromPE(pe);
// Page alignment/size
fseek(pe, peData->peHeaderOffset + 0x38, SEEK_SET);
peData->pageSize = get32BitFromPE(pe);
// Export tables
fseek(pe, peData->peHeaderOffset + 0x78, SEEK_SET);
peData->peExportInfo.count = (get32BitFromPE(pe) == 0 ? 0 : 1); // TODO: Actually read the data
// Import tables
fseek(pe, peData->peHeaderOffset + 0x80, SEEK_SET);
peData->peImportInfo.count = (get32BitFromPE(pe) == 0 ? 0 : 1); // TODO: Actually read the data
// TLS status (PE TLS is currently UNSUPPORTED, so if we find it, we'll need to abort)
fseek(pe, peData->peHeaderOffset + 0xC0, SEEK_SET);
peData->tlsAddr = get32BitFromPE(pe);
peData->tlsSize = get32BitFromPE(pe);
if(peData->tlsAddr != 0 || peData->tlsSize != 0) {return ERR_UNSUPPORTED_STRUCTURE;}
// Page RWX flags
int ret = getSectionRwxFlags(pe, &(peData->sections));
if(ret != 0) {return ret;}
return SUCCESS;
}

280
src/getdata/gethdrdata.c Normal file
View file

@ -0,0 +1,280 @@
// This file is part of SynthXEX, one component of the
// FreeChainXenon development toolchain
//
// Copyright (c) 2024-25 Aiden Isik
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "gethdrdata.h"
// Validate PE. This isn't thorough, but it's enough to catch any non-PE/360 files.
// I was considering merging this with getHdrData and mapPEToBasefile as we're
// basically reading the same data twice, but I think it's beneficial to have a
// dedicated place where we validate the input.
bool validatePE(FILE *pe, bool skipMachineCheck) // True if valid, else false
{
// Check if we have at least the size of a DOS header, so we don't overrun the PE
fseek(pe, 0, SEEK_END);
size_t finalOffset = ftell(pe);
if(finalOffset < 0x3C + 0x4)
{ return false; }
// Check magic
fseek(pe, 0, SEEK_SET);
uint16_t magic = get16BitFromPE(pe);
if(magic != 0x5A4D || errno != SUCCESS) // PE magic
{ return false; }
// Check if pointer to PE header is valid
fseek(pe, 0x3C, SEEK_SET);
size_t peHeaderOffset = get32BitFromPE(pe);
if(finalOffset < peHeaderOffset || errno != SUCCESS)
{ return false; }
// Check if the file is big enough to get size of optional header, and therefore size of whole PE header
if(finalOffset < 0x14 + 0x2)
{ return false; }
// Check section count
fseek(pe, peHeaderOffset + 0x6, SEEK_SET);
uint16_t sectionCount = get16BitFromPE(pe);
if(sectionCount == 0 || errno != SUCCESS)
{ return false; }
// Check if the file is large enough to contain the whole PE header
fseek(pe, peHeaderOffset + 0x14, SEEK_SET);
uint16_t sizeOfOptHdr = get16BitFromPE(pe);
// 0x18 == size of COFF header, 0x28 == size of one entry in section table
if(finalOffset < peHeaderOffset + 0x18 + sizeOfOptHdr + (sectionCount * 0x28) || errno != SUCCESS)
{ return false; }
// Check machine ID
fseek(pe, peHeaderOffset + 0x4, SEEK_SET);
// 0x1F2 == POWERPCBE
uint16_t machineID = get16BitFromPE(pe);
if((machineID != 0x1F2 && !skipMachineCheck) || errno != SUCCESS)
{ return false; }
// Check subsystem
fseek(pe, peHeaderOffset + 0x5C, SEEK_SET);
uint16_t subsystem = get16BitFromPE(pe);
if(subsystem != 0xE || errno != SUCCESS) // 0xE == XBOX
{ return false; }
// Check page size/alignment
fseek(pe, peHeaderOffset + 0x38, SEEK_SET);
// 4KiB and 64KiB are the only valid sizes
uint32_t pageSize = get32BitFromPE(pe);
if((pageSize != 0x1000 && pageSize != 0x10000) || errno != SUCCESS)
{ return false; }
// Check each raw offset + raw size in section table
fseek(pe, peHeaderOffset + 0x18 + sizeOfOptHdr + 0x10, SEEK_SET); // 0x10 == raw offset in entry
for(uint16_t i = 0; i < sectionCount; i++)
{
// If raw size + raw offset exceeds file size, PE is invalid
uint32_t rawSize = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return false; }
uint32_t rawOffset = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return false; }
if(finalOffset < rawSize + rawOffset)
{ return false; }
// Next entry
fseek(pe, 0x20, SEEK_CUR);
}
return true; // Checked enough, this is an Xbox 360 PE file
}
int getSectionInfo(FILE *pe, struct sections *sections)
{
fseek(pe, 0x3C, SEEK_SET);
uint32_t peOffset = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
fseek(pe, peOffset + 0x6, SEEK_SET); // 0x6 == section count
sections->count = get16BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
sections->section = calloc(sections->count, sizeof(struct section)); // free() is called for this in setdata
if(sections->section == NULL)
{ return ERR_OUT_OF_MEM; }
fseek(pe, peOffset + 0xF8, SEEK_SET); // 0xF8 == beginning of section table
for(uint16_t i = 0; i < sections->count; i++)
{
fseek(pe, 0x8, SEEK_CUR); // Seek to virtual size of section
sections->section[i].virtualSize = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
sections->section[i].rva = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
sections->section[i].rawSize = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
sections->section[i].offset = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
fseek(pe, 0xC, SEEK_CUR); // Now progress to characteristics, where we will check flags
uint32_t characteristics = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
if(characteristics & PE_SECTION_FLAG_EXECUTE)
{
sections->section[i].permFlag = XEX_SECTION_CODE | 0b10000; // | 0b(1)0000 == include size of 1
}
else if(characteristics & PE_SECTION_FLAG_WRITE || characteristics & PE_SECTION_FLAG_DISCARDABLE)
{ sections->section[i].permFlag = XEX_SECTION_RWDATA | 0b10000; }
else if(characteristics & PE_SECTION_FLAG_READ)
{ sections->section[i].permFlag = XEX_SECTION_RODATA | 0b10000; }
else
{ return ERR_MISSING_SECTION_FLAG; }
}
// Don't need to progress any more to get to beginning of next entry, as characteristics is last field
return SUCCESS;
}
int getHdrData(FILE *pe, struct peData *peData, uint8_t flags)
{
// No flags supported at this time (will be used for getting additional info, for e.g. other optional headers)
if(flags)
{ return ERR_UNKNOWN_DATA_REQUEST; }
// Getting PE header offset before we go any further..
fseek(pe, 0x3C, SEEK_SET);
peData->peHeaderOffset = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
// Number of sections
fseek(pe, peData->peHeaderOffset + 0x6, SEEK_SET);
peData->numberOfSections = get16BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
// Size of section table
peData->sectionTableSize = peData->numberOfSections * 0x28;
// Size of header
// 0x18 == size of COFF header, get16BitFromPE value == size of optional header
fseek(pe, peData->peHeaderOffset + 0x14, SEEK_SET);
peData->headerSize = (peData->peHeaderOffset + 1) + 0x18 + get16BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
// PE characteristics
fseek(pe, peData->peHeaderOffset + 0x16, SEEK_SET);
peData->characteristics = get16BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
// Entry point (RVA)
fseek(pe, peData->peHeaderOffset + 0x28, SEEK_SET);
peData->entryPoint = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
// Base address
fseek(pe, peData->peHeaderOffset + 0x34, SEEK_SET);
peData->baseAddr = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
// Page alignment/size
fseek(pe, peData->peHeaderOffset + 0x38, SEEK_SET);
peData->pageSize = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
// Export tables
fseek(pe, peData->peHeaderOffset + 0x78, SEEK_SET);
peData->peExportInfo.count = (get32BitFromPE(pe) == 0 ? 0 : 1); // TODO: Actually read the data
if(errno != SUCCESS)
{ return errno; }
// Import tables
fseek(pe, peData->peHeaderOffset + 0x80, SEEK_SET);
peData->peImportInfo.idtRVA = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
// TLS status (PE TLS is currently UNSUPPORTED, so if we find it, we'll need to abort)
fseek(pe, peData->peHeaderOffset + 0xC0, SEEK_SET);
peData->tlsAddr = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
peData->tlsSize = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
if(peData->tlsAddr != 0 || peData->tlsSize != 0)
{ return ERR_UNSUPPORTED_STRUCTURE; }
// Section info
int ret = getSectionInfo(pe, &(peData->sections));
if(ret != 0)
{ return ret; }
return SUCCESS;
}

View file

@ -1,7 +1,7 @@
// This file is part of SynthXEX, one component of the
// FreeChainXenon development toolchain
//
// Copyright (c) 2024 Aiden Isik
// Copyright (c) 2024-25 Aiden Isik
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@ -18,7 +18,6 @@
#pragma once
#include <sys/stat.h>
#include "../common/common.h"
#include "../common/datastorage.h"

206
src/getdata/getimports.c Normal file
View file

@ -0,0 +1,206 @@
// This file is part of SynthXEX, one component of the
// FreeChainXenon development toolchain
//
// Copyright (c) 2025 Aiden Isik
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "getimports.h"
int getImports(FILE *pe, struct peData *peData)
{
// Make sure the peImportInfo struct is blank, except the IDT RVA
memset(&(peData->peImportInfo.tableCount), 0, sizeof(struct peImportInfo) - sizeof(uint32_t));
// If there is no IDT, just skip handling imports
if(peData->peImportInfo.idtRVA == 0)
{ return SUCCESS; }
// Seek to the IDT and read the first entry
uint32_t idtOffset = rvaToOffset(peData->peImportInfo.idtRVA, &(peData->sections));
if(idtOffset == 0)
{ return ERR_INVALID_RVA_OR_OFFSET; }
if(fseek(pe, idtOffset, SEEK_SET) != 0)
{ return ERR_FILE_READ; }
uint32_t *currentIDT = malloc(5 * sizeof(uint32_t));
if(currentIDT == NULL)
{ return ERR_OUT_OF_MEM; }
uint32_t *blankIDT = calloc(5, sizeof(uint32_t)); // Blank IDT for comparisons
if(blankIDT == NULL)
{
nullAndFree((void **)&currentIDT);
return ERR_OUT_OF_MEM;
}
if(fread(currentIDT, sizeof(uint32_t), 5, pe) < 5)
{
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return ERR_FILE_READ;
}
// While the IDT is not blank, process it
for(uint32_t i = 0; memcmp(currentIDT, blankIDT, 5 * sizeof(uint32_t)) != 0; i++)
{
// Allocate space for the current table data
peData->peImportInfo.tableCount++;
peData->peImportInfo.tables = realloc(peData->peImportInfo.tables, (i + 1) * sizeof(struct peImportTable));
if(peData->peImportInfo.tables == NULL)
{
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return ERR_OUT_OF_MEM;
}
memset(&(peData->peImportInfo.tables[i]), 0, sizeof(struct peImportTable)); // Make sure it's blank
#ifdef BIG_ENDIAN_SYSTEM
// Byteswap the IDT fields
for(uint8_t j = 0; j < 5; j++)
{ currentIDT[j] = __builtin_bswap32(currentIDT[j]); }
#endif
// Retrieve the name pointed to by the table
uint32_t savedOffset = ftell(pe);
uint32_t tableNameOffset = rvaToOffset(currentIDT[3], &(peData->sections));
if(tableNameOffset == 0)
{
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return ERR_INVALID_RVA_OR_OFFSET;
}
if(fseek(pe, tableNameOffset, SEEK_SET) != 0)
{
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return ERR_FILE_READ;
}
// Allocating is expensive, go 16 bytes at a time to avoid excessive realloc calls
for(uint32_t j = 0;; j += 16)
{
peData->peImportInfo.tables[i].name = realloc(peData->peImportInfo.tables[i].name, (j + 16) * sizeof(char));
if(peData->peImportInfo.tables[i].name == NULL)
{
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return ERR_OUT_OF_MEM;
}
if(fread(peData->peImportInfo.tables[i].name + j, sizeof(char), 16, pe) < 16)
{
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return ERR_FILE_READ;
}
// Check for null terminator
for(uint32_t k = j; k < j + 16; k++)
if(peData->peImportInfo.tables[i].name[k] == '\0')
{ goto doneGettingTableName; }
}
doneGettingTableName:
// Store the IAT RVA for this table
peData->peImportInfo.tables[i].rva = currentIDT[4];
// Seek to the IAT and read the first entry
uint32_t iatOffset = rvaToOffset(currentIDT[4], &(peData->sections));
if(iatOffset == 0)
{
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return ERR_INVALID_RVA_OR_OFFSET;
}
if(fseek(pe, iatOffset, SEEK_SET) != 0)
{
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return ERR_FILE_READ;
}
uint32_t currentImport;
if(fread(&currentImport, sizeof(uint32_t), 1, pe) < 1)
{
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return ERR_FILE_READ;
}
// While the import is not blank, process it
for(int j = 0; currentImport != 0; j++)
{
#ifdef BIG_ENDIAN_SYSTEM
// Byteswap the import
currentImport = __builtin_bswap32(currentImport);
#endif
// Allocate space for the current import
peData->peImportInfo.tables[i].importCount++;
peData->peImportInfo.tables[i].imports = realloc(peData->peImportInfo.tables[i].imports, (j + 1) * sizeof(struct peImport));
// Store the address of the current import entry in iatAddr
uint32_t currentImportRVA = offsetToRVA(ftell(pe) - 4, &(peData->sections));
if(currentImportRVA == 0)
{
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return ERR_INVALID_RVA_OR_OFFSET;
}
peData->peImportInfo.tables[i].imports[j].iatAddr = peData->baseAddr + currentImportRVA;
// Read the next import
if(fread(&currentImport, sizeof(uint32_t), 1, pe) < 1)
{
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return ERR_FILE_READ;
}
}
// Add table's import count to total and return to next IDT entry
peData->peImportInfo.totalImportCount += peData->peImportInfo.tables[i].importCount;
if(fseek(pe, savedOffset, SEEK_SET) != 0)
{
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return ERR_FILE_READ;
}
// Read next IDT
if(fread(currentIDT, sizeof(uint32_t), 5, pe) < 5)
{ return ERR_FILE_READ; }
}
nullAndFree((void **)&currentIDT);
nullAndFree((void **)&blankIDT);
return SUCCESS;
}

24
src/getdata/getimports.h Normal file
View file

@ -0,0 +1,24 @@
// This file is part of SynthXEX, one component of the
// FreeChainXenon development toolchain
//
// Copyright (c) 2025 Aiden Isik
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include "../common/common.h"
#include "../common/datastorage.h"
int getImports(FILE *pe, struct peData *peData);

View file

@ -16,10 +16,14 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// TODO: Harden all functions against bad data, unallocated data (check for NULL), etc.
// Some functions are not robust as I wanted things up and running as quickly as possible.
#include "common/common.h"
#include "common/datastorage.h"
#include "pemapper/pemapper.h"
#include "getdata/getdata.h"
#include "getdata/gethdrdata.h"
#include "getdata/getimports.h"
#include "setdata/populateheaders.h"
#include "setdata/pagedescriptors.h"
#include "setdata/optheaders.h"
@ -86,8 +90,8 @@ void dispLibs()
void dispVer()
{
printf("\n%s\n\nThe XEX builder of the FreeChainXenon project\n\n", VERSION_STRING);
printf("Copyright (c) %s Aiden Isik\n\nThis program is free software: you can redistribute it and/or modify\n", COPYRIGHT);
printf("\n%s\n\nThe XEX builder of the FreeChainXenon project\n\n", SYNTHXEX_VERSION_STRING);
printf("Copyright (c) %s Aiden Isik\n\nThis program is free software: you can redistribute it and/or modify\n", SYNTHXEX_COPYRIGHT);
printf("it under the terms of the GNU Affero General Public License as published by\n");
printf("the Free Software Foundation, either version 3 of the License, or\n");
printf("(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\n");
@ -106,22 +110,86 @@ void dispHelp(char **argv)
printf("-l,\t--libs,\t\t\tShow licensing information of libraries used\n");
printf("-s,\t--skip-machine-check,\tSkip the PE file machine ID check\n");
printf("-i,\t--input,\t\tSpecify input PE file path\n");
printf("-o,\t--output,\t\tSpecify output XEX file path\n\n");
printf("-o,\t--output,\t\tSpecify output XEX file path\n");
printf("-t,\t--type,\t\t\tOverride automatic executable type detection\n\t\t\t\t(options: title, titledll, sysdll, dll)\n\n");
}
void handleError(int ret)
{
switch(ret)
{
case ERR_UNKNOWN_DATA_REQUEST:
fprintf(stderr, "%s ERROR: Internal error getting data from PE file. THIS IS A BUG, please report it. Aborting.\n", SYNTHXEX_PRINT_STEM);
break;
case ERR_FILE_READ:
fprintf(stderr, "%s ERROR: Failed to read data from PE file. Aborting.\n", SYNTHXEX_PRINT_STEM);
break;
case ERR_FILE_WRITE:
fprintf(stderr, "%s ERROR: Failed to write data to file. Aborting.\n", SYNTHXEX_PRINT_STEM);
break;
case ERR_OUT_OF_MEM:
fprintf(stderr, "%s ERROR: Out of memory. Aborting.\n", SYNTHXEX_PRINT_STEM);
break;
case ERR_MISSING_SECTION_FLAG:
fprintf(stderr, "%s ERROR: R/W/X flag missing from PE section. Aborting.\n", SYNTHXEX_PRINT_STEM);
break;
case ERR_UNSUPPORTED_STRUCTURE:
fprintf(stderr, "%s ERROR: Encountered an unsupported data structure in PE. Aborting.\n", SYNTHXEX_PRINT_STEM);
break;
case ERR_INVALID_RVA_OR_OFFSET:
fprintf(stderr, "%s ERROR: Invalid RVA or offset found. Aborting.\n", SYNTHXEX_PRINT_STEM);
break;
case ERR_INVALID_IMPORT_NAME:
fprintf(stderr, "%s ERROR: Invalid import name found. Aborting.\n", SYNTHXEX_PRINT_STEM);
break;
case ERR_DATA_OVERFLOW:
fprintf(stderr, "%s ERROR: Data overflow. Aborting.\n", SYNTHXEX_PRINT_STEM);
break;
default:
fprintf(stderr, "%s ERROR: Unknown error: %d. Aborting.\n", SYNTHXEX_PRINT_STEM, ret);
break;
}
}
int main(int argc, char **argv)
{
static struct option longOptions[] =
{
{"help", no_argument, 0, 0},
{"version", no_argument, 0, 0},
{"libs", no_argument, 0, 0},
{"skip-machine-check", no_argument, 0, 0},
{"input", required_argument, 0, 0},
{"output", required_argument, 0, 0},
{ "help", no_argument, 0, 'h' },
{ "version", no_argument, 0, 'v' },
{ "libs", no_argument, 0, 'l' },
{ "skip-machine-check", no_argument, 0, 's' },
{ "input", required_argument, 0, 'i' },
{ "output", required_argument, 0, 'o' },
{ "type", required_argument, 0, 't' },
{ 0, 0, 0, 0 }
};
struct offsets *offsets = calloc(1, sizeof(struct offsets));
struct xexHeader *xexHeader = calloc(1, sizeof(struct xexHeader));
struct secInfoHeader *secInfoHeader = calloc(1, sizeof(struct secInfoHeader));
struct peData *peData = calloc(1, sizeof(struct peData));
struct optHeaderEntries *optHeaderEntries = calloc(1, sizeof(struct optHeaderEntries));
struct optHeaders *optHeaders = calloc(1, sizeof(struct optHeaders));
if(offsets == NULL || xexHeader == NULL || secInfoHeader == NULL ||
peData == NULL || optHeaderEntries == NULL || optHeaders == NULL)
{
printf("%s ERROR: Out of memory. Aborting\n", SYNTHXEX_PRINT_STEM);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData,
&optHeaderEntries, &optHeaders);
return -1;
}
int optIndex = 0;
int option = 0;
@ -132,185 +200,194 @@ int main(int argc, char **argv)
char *pePath = NULL;
char *xexfilePath = NULL;
while((option = getopt_long(argc, argv, "hvlsi:o:", longOptions, &optIndex)) != -1)
while((option = getopt_long(argc, argv, "hvlsi:o:t:", longOptions, &optIndex)) != -1)
{
if(option == 'h' || option == '?' || (option == 0 && strcmp(longOptions[optIndex].name, "help") == 0))
{
dispHelp(argv);
return SUCCESS;
}
else if(option == 'v' || (option == 0 && strcmp(longOptions[optIndex].name, "version") == 0))
switch(option)
{
case 'v':
dispVer();
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData,
&optHeaderEntries, &optHeaders);
return SUCCESS;
}
else if(option == 'l' || (option == 0 && strcmp(longOptions[optIndex].name, "libs") == 0))
{
case 'l':
dispLibs();
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData,
&optHeaderEntries, &optHeaders);
return SUCCESS;
}
else if(option == 's' || (option == 0 && strcmp(longOptions[optIndex].name, "skip-machine-check") == 0))
{
printf("%s WARNING: Skipping machine ID check.\n", PRINT_STEM);
case 's':
printf("%s WARNING: Skipping machine ID check.\n", SYNTHXEX_PRINT_STEM);
skipMachineCheck = true;
}
else if(option == 'i' || (option == 0 && strcmp(longOptions[optIndex].name, "input") == 0))
{
break;
case 'i':
gotInput = true;
pePath = malloc(strlen(optarg) + 1);
if(pePath == NULL)
{
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
if(xexfilePath != NULL) {free(xexfilePath);}
printf("%s ERROR: Out of memory. Aborting.\n", SYNTHXEX_PRINT_STEM);
nullAndFree((void **)&xexfilePath);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData,
&optHeaderEntries, &optHeaders);
return -1;
}
strncpy(pePath, optarg, strlen(optarg) + 1);
}
else if(option == 'o' || (option == 0 && strcmp(longOptions[optIndex].name, "output") == 0))
{
strcpy(pePath, optarg);
break;
case 'o':
gotOutput = true;
xexfilePath = malloc(strlen(optarg) + 1);
if(xexfilePath == NULL)
{
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
if(pePath != NULL) {free(pePath);}
printf("%s ERROR: Out of memory. Aborting.\n", SYNTHXEX_PRINT_STEM);
nullAndFree((void **)&pePath);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData,
&optHeaderEntries, &optHeaders);
return -1;
}
strncpy(xexfilePath, optarg, strlen(optarg) + 1);
strcpy(xexfilePath, optarg);
break;
case 't':
if(strcmp(optarg, "title") == 0)
{ xexHeader->moduleFlags = XEX_MOD_FLAG_TITLE; }
else if(strcmp(optarg, "titledll") == 0)
{ xexHeader->moduleFlags = XEX_MOD_FLAG_TITLE | XEX_MOD_FLAG_DLL; }
else if(strcmp(optarg, "sysdll") == 0)
{ xexHeader->moduleFlags = XEX_MOD_FLAG_EXPORTS | XEX_MOD_FLAG_DLL; }
else if(strcmp(optarg, "dll") == 0)
{ xexHeader->moduleFlags = XEX_MOD_FLAG_DLL; }
else
{
printf("%s ERROR: Invalid type override \"%s\" (valid: title, titledll, sysdll, dll). Aborting.\n",
SYNTHXEX_PRINT_STEM, optarg);
nullAndFree((void **)&pePath);
nullAndFree((void **)&xexfilePath);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData,
&optHeaderEntries, &optHeaders);
return -1;
}
break;
case 'h':
default:
dispHelp(argv);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData,
&optHeaderEntries, &optHeaders);
return SUCCESS;
}
}
printf("%s This is %s. Copyright (c) %s Aiden Isik.\n", PRINT_STEM, VERSION_STRING, COPYRIGHT);
printf("%s This program is free/libre software. Run \"%s --version\" for info.\n\n", PRINT_STEM, argv[0]);
printf("%s This is %s. Copyright (c) %s Aiden Isik.\n", SYNTHXEX_PRINT_STEM, SYNTHXEX_VERSION_STRING, SYNTHXEX_COPYRIGHT);
printf("%s This program is free/libre software. Run \"%s --version\" for info.\n\n", SYNTHXEX_PRINT_STEM, argv[0]);
// Check we got everything we need
if(!gotInput)
{
if(gotOutput)
{
free(xexfilePath);
}
{ nullAndFree((void **)&xexfilePath); }
printf("%s ERROR: PE input expected but not found. Aborting.\n", PRINT_STEM);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
printf("%s ERROR: PE input expected but not found. Aborting.\n", SYNTHXEX_PRINT_STEM);
return -1;
}
else if(!gotOutput)
{
if(gotInput)
{
free(pePath);
}
{ nullAndFree((void **)&pePath); }
printf("%s ERROR: XEX file output expected but not found. Aborting.\n", PRINT_STEM);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
printf("%s ERROR: XEX file output expected but not found. Aborting.\n", SYNTHXEX_PRINT_STEM);
return -1;
}
// Opening the files now that they've been validated
FILE *pe = fopen(pePath, "rb");
if(pe == NULL)
{
printf("%s ERROR: Failed to open PE file. Do you have read permissions? Aborting.\n", PRINT_STEM);
free(pePath);
free(xexfilePath);
printf("%s ERROR: Failed to open PE file. Do you have read permissions? Aborting.\n", SYNTHXEX_PRINT_STEM);
nullAndFree((void **)&pePath);
nullAndFree((void **)&xexfilePath);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
return -1;
}
free(pePath);
nullAndFree((void **)&pePath);
FILE *xex = fopen(xexfilePath, "wb+");
if(xex == NULL)
{
printf("%s ERROR: Failed to create XEX file. Do you have write permissions? Aborting.\n", PRINT_STEM);
printf("%s ERROR: Failed to create XEX file. Do you have write permissions? Aborting.\n", SYNTHXEX_PRINT_STEM);
fclose(pe);
free(xexfilePath);
nullAndFree((void **)&xexfilePath);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
return -1;
}
// Don't free this yet. We need it to determine where we can put the mapped basefile.
// There *are* ways to get the file path from file pointer, but none of them are portable.
//free(xexfilePath);
// Keep xexfilePath for later use
// Do NOT free xexfilePath here yet
int ret;
int ret = 0;
struct offsets offsets;
struct xexHeader xexHeader;
struct secInfoHeader secInfoHeader;
struct peData peData;
struct optHeaderEntries optHeaderEntries;
struct optHeaders optHeaders;
// Make sure the import library size is initially zero
optHeaders.importLibraries.size = 0;
printf("%s Validating PE file...\n", PRINT_STEM);
printf("%s Validating PE file...\n", SYNTHXEX_PRINT_STEM);
if(!validatePE(pe, skipMachineCheck))
{
printf("%s ERROR: Input PE is not Xbox 360 PE. Aborting.\n", PRINT_STEM);
free(xexfilePath);
printf("%s ERROR: Input PE is not Xbox 360 PE. Aborting.\n", SYNTHXEX_PRINT_STEM);
nullAndFree((void **)&xexfilePath);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(pe);
fclose(xex);
return -1;
}
printf("%s PE valid!\n", PRINT_STEM);
printf("%s PE valid!\n", SYNTHXEX_PRINT_STEM);
// Reading in header data from PE
printf("%s Retrieving header data from PE...\n", PRINT_STEM);
ret = getHdrData(pe, &peData, 0);
printf("%s Retrieving header data from PE...\n", SYNTHXEX_PRINT_STEM);
ret = getHdrData(pe, peData, 0);
if(ret == ERR_UNKNOWN_DATA_REQUEST)
if(ret != SUCCESS)
{
printf("%s ERROR: Internal error getting data from PE file. THIS IS A BUG, please report it. Aborting.\n", PRINT_STEM);
fclose(pe);
fclose(xex);
return -1;
}
else if(ret == ERR_FILE_READ)
{
printf("%s ERROR: Failed to read data from PE file. Aborting.\n", PRINT_STEM);
fclose(pe);
fclose(xex);
return -1;
}
else if(ret == ERR_OUT_OF_MEM)
{
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
fclose(pe);
fclose(xex);
return -1;
}
else if(ret == ERR_MISSING_SECTION_FLAG)
{
printf("%s ERROR: R/W/X flag missing from PE section. Aborting.\n", PRINT_STEM);
fclose(pe);
fclose(xex);
return -1;
}
else if(ret == ERR_UNSUPPORTED_STRUCTURE)
{
printf("%s ERROR: Encountered an unsupported data structure in PE. Aborting.\n", PRINT_STEM);
handleError(ret);
nullAndFree((void **)&xexfilePath);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(pe);
fclose(xex);
return -1;
}
printf("%s Got header data from PE!\n", PRINT_STEM);
printf("%s Got header data from PE!\n", SYNTHXEX_PRINT_STEM);
// Determine the path we should save the basefile at and open it.
printf("%s Creating basefile from PE...\n", PRINT_STEM);
printf("%s Retrieving import data from PE...\n", SYNTHXEX_PRINT_STEM);
ret = getImports(pe, peData);
if(ret != SUCCESS)
{
handleError(ret);
nullAndFree((void **)&xexfilePath);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(pe);
fclose(xex);
return -1;
}
printf("%s Got import data from PE!\n", SYNTHXEX_PRINT_STEM);
printf("%s Creating basefile from PE...\n", SYNTHXEX_PRINT_STEM);
char *basefilePath = malloc((strlen(xexfilePath) + strlen(".basefile") + 1) * sizeof(char));
if(basefilePath == NULL)
if(!basefilePath)
{
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
free(xexfilePath);
printf("%s ERROR: Out of memory. Aborting.\n", SYNTHXEX_PRINT_STEM);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
nullAndFree((void **)&xexfilePath);
fclose(pe);
fclose(xex);
return -1;
@ -320,94 +397,138 @@ int main(int argc, char **argv)
strcat(basefilePath, ".basefile");
FILE *basefile = fopen(basefilePath, "wb+");
free(xexfilePath); // *Now* we're done with it.
free(basefilePath);
nullAndFree((void **)&xexfilePath);
nullAndFree((void **)&basefilePath);
if(basefile == NULL)
if(!basefile)
{
printf("%s ERROR: Could not create basefile. Do you have write permission? Aborting.\n", PRINT_STEM);
printf("%s ERROR: Could not create basefile. Do you have write permission? Aborting.\n", SYNTHXEX_PRINT_STEM);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(pe);
fclose(xex);
return -1;
}
// Map the PE into the basefile (RVAs become offsets)
ret = mapPEToBasefile(pe, basefile, &peData);
ret = mapPEToBasefile(pe, basefile, peData);
fclose(pe);
if(ret == ERR_OUT_OF_MEM)
if(ret != SUCCESS)
{
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
handleError(ret);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(basefile);
fclose(xex);
return -1;
}
printf("%s Created basefile!\n", PRINT_STEM);
printf("%s Created basefile!\n", SYNTHXEX_PRINT_STEM);
// Setting final XEX data structs
printf("%s Building XEX header...\n", PRINT_STEM);
setXEXHeader(&xexHeader, &peData);
printf("%s Building security header...\n", SYNTHXEX_PRINT_STEM);
ret = setSecInfoHeader(secInfoHeader, peData);
printf("%s Building security header...\n", PRINT_STEM);
setSecInfoHeader(&secInfoHeader, &peData);
printf("%s Setting page descriptors...\n", PRINT_STEM);
ret = setPageDescriptors(basefile, &peData, &secInfoHeader);
if(ret == ERR_OUT_OF_MEM)
if(ret != SUCCESS)
{
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
handleError(ret);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(basefile);
fclose(xex);
return -1;
}
printf("%s Building optional headers...\n", PRINT_STEM);
ret = setOptHeaders(&secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
printf("%s Setting page descriptors...\n", SYNTHXEX_PRINT_STEM);
ret = setPageDescriptors(basefile, peData, secInfoHeader);
if(ret == ERR_OUT_OF_MEM)
if(ret != SUCCESS)
{
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
handleError(ret);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(basefile);
fclose(xex);
return -1;
}
// Setting data positions...
printf("%s Aligning data...\n", PRINT_STEM);
ret = placeStructs(&offsets, &xexHeader, &optHeaderEntries, &secInfoHeader, &optHeaders);
// Done with this now
freeSectionsStruct(&(peData->sections));
if(ret == ERR_OUT_OF_MEM)
printf("%s Building optional headers...\n", SYNTHXEX_PRINT_STEM);
ret = setOptHeaders(secInfoHeader, peData, optHeaderEntries, optHeaders);
if(ret != SUCCESS)
{
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
handleError(ret);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(basefile);
fclose(xex);
return -1;
}
printf("%s Building XEX header...\n", SYNTHXEX_PRINT_STEM);
ret = setXEXHeader(xexHeader, optHeaderEntries, peData);
if(ret != SUCCESS)
{
handleError(ret);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(basefile);
fclose(xex);
return -1;
}
// Done with this now
freePeImportInfoStruct(&(peData->peImportInfo));
// Setting data positions
printf("%s Aligning data...\n", SYNTHXEX_PRINT_STEM);
ret = placeStructs(offsets, xexHeader, optHeaderEntries, secInfoHeader, optHeaders);
if(ret != SUCCESS)
{
handleError(ret);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(basefile);
fclose(xex);
return -1;
}
// We're done with this now
freePeDataStruct(&peData);
// Write out all of the XEX data to file
printf("%s Writing data to XEX file...\n", PRINT_STEM);
ret = writeXEX(&xexHeader, &optHeaderEntries, &secInfoHeader, &optHeaders, &offsets, basefile, xex);
printf("%s Writing XEX...\n", SYNTHXEX_PRINT_STEM);
ret = writeXEX(xexHeader, optHeaderEntries, secInfoHeader, optHeaders, offsets, basefile, xex);
if(ret == ERR_OUT_OF_MEM)
if(ret != SUCCESS)
{
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
handleError(ret);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(basefile);
fclose(xex);
return -1;
}
printf("%s Main data written to XEX file!\n", PRINT_STEM);
// Final pass (sets & writes header hash)
printf("%s Calculating and writing header SHA1...\n", PRINT_STEM);
setHeaderSha1(xex);
printf("%s Header SHA1 written!\n", PRINT_STEM);
printf("%s Calculating and writing header SHA1...\n", SYNTHXEX_PRINT_STEM);
ret = setHeaderSha1(xex);
if(ret != SUCCESS)
{
handleError(ret);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(basefile);
fclose(xex);
return -1;
}
printf("%s XEX built. Have a nice day!\n\n", PRINT_STEM);
printf("%s Header SHA1 written!\n", SYNTHXEX_PRINT_STEM);
// Free files
fclose(xex);
fclose(basefile);
// Free structs
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
printf("%s XEX built. Have a nice day!\n\n", SYNTHXEX_PRINT_STEM);
return SUCCESS;
}

View file

@ -26,63 +26,159 @@ struct sectionInfo
uint32_t offset;
};
// Strips the ordinal flags from IAT entries, and swaps them to big endian
// Also adds module indexes to them
int xenonifyIAT(FILE *basefile, struct peData *peData)
{
// Loop through each import table and handle their IAT entries
for(uint32_t i = 0; i < peData->peImportInfo.tableCount; i++)
{
// Seek to the first IAT entry for this table
if(fseek(basefile, peData->peImportInfo.tables[i].rva, SEEK_SET) != 0)
{ return ERR_FILE_READ; }
// Loop through each import and handle it's IAT entry
for(uint32_t j = 0; j < peData->peImportInfo.tables[i].importCount; j++)
{
// Read in the current IAT entry
uint32_t iatEntry = get32BitFromPE(basefile);
if(errno != SUCCESS)
{ return ERR_FILE_READ; }
// Seek back so we can write back to the same place
if(fseek(basefile, -0x4, SEEK_CUR) != 0)
{ return ERR_FILE_READ; }
// Xenonify the IAT entry
iatEntry &= ~PE_IMPORT_ORDINAL_FLAG; // Strip the import by ordinal flag
iatEntry |= (i & 0x000000FF) << 16; // Add the module index
// Write back out as big endian (TODO: make a utility function for this like get32BitFromPE)
#ifdef LITTLE_ENDIAN_SYSTEM
iatEntry = __builtin_bswap32(iatEntry);
#endif
if(fwrite(&iatEntry, sizeof(uint32_t), 1, basefile) < 1)
{ return ERR_FILE_WRITE; }
// Call file positioning function between reads and writes to the same file.
// This is mandated by the C standard.
fseek(basefile, 0, SEEK_CUR);
}
}
return SUCCESS;
}
// Maps the PE file into the basefile (RVAs become offsets)
int mapPEToBasefile(FILE *pe, FILE *basefile, struct peData *peData)
{
// Retrieve data for each section
// Retrieve the section info from PE (TODO: use the data we get from getHdrData for this, right now we're duplicating work)
struct sectionInfo *sectionInfo = malloc(peData->numberOfSections *sizeof(struct sectionInfo));
fseek(pe, (peData->headerSize - 1) + 0x8, SEEK_SET); // Seek to the first section in the section table at virtualSize
if(!sectionInfo)
{ return ERR_OUT_OF_MEM; }
// Seek to the first section in the section table at virtualSize
if(fseek(pe, (peData->headerSize - 1) + 0x8, SEEK_SET) != 0)
{ return ERR_FILE_READ; }
for(uint16_t i = 0; i < peData->numberOfSections; i++)
{
sectionInfo[i].virtualSize = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
sectionInfo[i].rva = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
sectionInfo[i].rawSize = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
sectionInfo[i].offset = get32BitFromPE(pe);
fseek(pe, 0x18, SEEK_CUR); // Seek to next entry at virtualSize
if(errno != SUCCESS)
{ return errno; }
// Seek to the next entry at virtualSize
if(fseek(pe, 0x18, SEEK_CUR) != 0)
{ return ERR_FILE_READ; }
}
// Copy the PE header and section table to the basefile verbatim
fseek(pe, 0, SEEK_SET);
uint8_t *buffer = malloc(peData->headerSize + peData->sectionTableSize);
if(buffer == NULL) {return ERR_OUT_OF_MEM;}
if(fseek(pe, 0, SEEK_SET) != 0)
{ return ERR_FILE_READ; }
fread(buffer, sizeof(uint8_t), peData->headerSize + peData->sectionTableSize, pe);
fwrite(buffer, sizeof(uint8_t), peData->headerSize + peData->sectionTableSize, basefile);
// Copy the PE header and section table to the basefile verbatim
uint8_t *buffer = malloc(peData->headerSize + peData->sectionTableSize);
if(!buffer)
{ return ERR_OUT_OF_MEM; }
size_t totalHeader = peData->headerSize + peData->sectionTableSize;
if(fread(buffer, 1, totalHeader, pe) != totalHeader)
{ return ERR_FILE_READ; }
if(fwrite(buffer, 1, totalHeader, basefile) != totalHeader)
{ return ERR_FILE_READ; }
// Now map the sections and write them
for(uint16_t i = 0; i < peData->numberOfSections; i++)
{
buffer = realloc(buffer, sectionInfo[i].rawSize * sizeof(uint8_t));
if(buffer == NULL) {return ERR_OUT_OF_MEM;}
buffer = realloc(buffer, sectionInfo[i].rawSize);
fseek(pe, sectionInfo[i].offset, SEEK_SET);
fread(buffer, sizeof(uint8_t), sectionInfo[i].rawSize, pe);
if(!buffer)
{ return ERR_OUT_OF_MEM; }
fseek(basefile, sectionInfo[i].rva, SEEK_SET);
fwrite(buffer, sizeof(uint8_t), sectionInfo[i].rawSize, basefile);
if(fseek(pe, sectionInfo[i].offset, SEEK_SET) != 0)
{ return ERR_FILE_READ; }
if(fread(buffer, 1, sectionInfo[i].rawSize, pe) != sectionInfo[i].rawSize)
{ return ERR_FILE_READ; }
if(fseek(basefile, sectionInfo[i].rva, SEEK_SET) != 0)
{ return ERR_FILE_READ; }
if(fwrite(buffer, 1, sectionInfo[i].rawSize, basefile) != sectionInfo[i].rawSize)
{ return ERR_FILE_READ; }
}
// Pad the rest of the final page with zeroes, we can achieve this by seeking
// to the end and placing a single zero there (unless the data runs all the way up to the end!)
uint32_t currentOffset = ftell(basefile);
uint32_t nextAligned = getNextAligned(currentOffset, peData->pageSize) - 0x1;
uint32_t nextAligned = getNextAligned(currentOffset, peData->pageSize) - 1;
if(nextAligned != currentOffset)
{
buffer = realloc(buffer, 1 * sizeof(uint8_t));
if(buffer == NULL) {return ERR_OUT_OF_MEM;}
buffer = realloc(buffer, 1);
if(!buffer)
{ return ERR_OUT_OF_MEM; }
buffer[0] = 0;
fseek(basefile, nextAligned, SEEK_SET);
fwrite(buffer, sizeof(uint8_t), 1, basefile);
if(fseek(basefile, nextAligned, SEEK_SET) != 0)
{ return ERR_FILE_READ; }
if(fwrite(buffer, 1, 1, basefile) != 1)
{ return ERR_FILE_READ; }
}
// Make sure to update the PE (basefile) size
peData->size = ftell(basefile);
// We're done with these, free them
free(buffer);
free(sectionInfo);
// We're done with these now, free them
nullAndFree((void **)&buffer);
nullAndFree((void **)&sectionInfo);
return SUCCESS;
// While we're writing the basefile, let's do the required modifications to the IAT.
// We can skip the return check because this is the last function call.
// The outcome of this is the outcome of the whole function.
return xenonifyIAT(basefile, peData);
}

View file

@ -27,9 +27,14 @@ struct importLibIdcs
int setOptHeaderOffsets(struct offsets *offsets, struct optHeaderEntries *optHeaderEntries, struct optHeaders *optHeaders, uint32_t *currentOffset, struct importLibIdcs *importLibIdcs)
{
offsets->optHeaders = calloc(optHeaderEntries->count, sizeof(uint32_t)); // Calloc because 0 values will be used to determine if a header is not present.
if(offsets->optHeaders == NULL) {return ERR_OUT_OF_MEM;}
uint32_t sepHeader = 0; // Separate header iterator, i.e. one with it's data outwith the entries
// Calloc because 0 values will be used to determine if a header is not present.
offsets->optHeaders = calloc(optHeaderEntries->count, sizeof(uint32_t));
if(offsets->optHeaders == NULL)
{ return ERR_OUT_OF_MEM; }
// Separate header iterator, i.e. one with it's data outwith the entries
uint32_t sepHeader = 0;
for(uint32_t i = 0; i < optHeaderEntries->count; i++)
{
@ -62,7 +67,6 @@ int setOptHeaderOffsets(struct offsets *offsets, struct optHeaderEntries *optHea
return SUCCESS;
}
// Todo in future: implement a dynamic optional header selection mechanism instead of hard-coding the basic 5
int placeStructs(struct offsets *offsets, struct xexHeader *xexHeader, struct optHeaderEntries *optHeaderEntries, struct secInfoHeader *secInfoHeader, struct optHeaders *optHeaders)
{
// XEX Header
@ -75,22 +79,19 @@ int placeStructs(struct offsets *offsets, struct xexHeader *xexHeader, struct op
currentOffset += optHeaderEntries->count *sizeof(struct optHeaderEntry);
// Security header
currentOffset = getNextAligned(currentOffset, 0x8); // 8-byte alignment for these headers etc
currentOffset = getNextAligned(currentOffset, 0x8); // 8-byte alignment for these headers, at least 8 bytes beyond end of optional header entries
offsets->secInfoHeader = currentOffset;
xexHeader->secInfoOffset = currentOffset;
currentOffset += (sizeof(struct secInfoHeader) - sizeof(void *)) + (secInfoHeader->pageDescCount *sizeof(struct pageDescriptor));
// Optional headers (minus imports)
struct importLibIdcs importLibIdcs;
uint32_t importLibsIdx; // Entry in opt header entries of import libs
int ret = setOptHeaderOffsets(offsets, optHeaderEntries, optHeaders, &currentOffset, &importLibIdcs);
if(ret != SUCCESS)
{
return ret;
}
{ return ret; }
//currentOffset += optHeaders->importLibraries.size; // Reserving bytes for imports
currentOffset += optHeaders->importLibraries.size; // Reserving bytes for imports
// PE basefile
currentOffset = getNextAligned(currentOffset, 0x1000); // 4KiB alignment for basefile
@ -98,8 +99,11 @@ int placeStructs(struct offsets *offsets, struct xexHeader *xexHeader, struct op
xexHeader->peOffset = currentOffset;
// Imports, the end of this header is aligned to the start of the basefile, so they are a special case
//offsets->optHeaders[importLibIdcs.header] = offsets->basefile - optHeaders->importLibraries.size;
//optHeaderEntries->optHeaderEntry[importLibIdcs.entry].dataOrOffset = offsets->optHeaders[importLibIdcs.header];
if(optHeaders->importLibraries.tableCount > 0)
{
offsets->optHeaders[importLibIdcs.header] = offsets->basefile - optHeaders->importLibraries.size;
optHeaderEntries->optHeaderEntry[importLibIdcs.entry].dataOrOffset = offsets->optHeaders[importLibIdcs.header];
}
return SUCCESS;
}

View file

@ -1,7 +1,7 @@
// This file is part of SynthXEX, one component of the
// FreeChainXenon development toolchain
//
// Copyright (c) 2024 Aiden Isik
// Copyright (c) 2024-25 Aiden Isik
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@ -36,9 +36,255 @@ void setTLSInfo(struct tlsInfo *tlsInfo)
tlsInfo->rawDataSize = 0x0;
}
int setImportLibsInfo(struct importLibraries *importLibraries, struct peImportInfo *peImportInfo, struct secInfoHeader *secInfoHeader)
{
// Set table count and allocate enough memory for all tables
importLibraries->tableCount = peImportInfo->tableCount;
secInfoHeader->importTableCount = peImportInfo->tableCount;
importLibraries->importTables = calloc(importLibraries->tableCount, sizeof(struct importTable));
if(!importLibraries->importTables)
{ return ERR_OUT_OF_MEM; }
if(peImportInfo->tableCount <= 0 || peImportInfo->tableCount > 65535)
{ return ERR_OUT_OF_MEM; }
// Use this to avoid dereferencing an unaligned pointer
struct importTable *importTables = importLibraries->importTables;
// Initialise the size of the import libraries to just the size of the header (- 2 * sizeof(void*) to exclude addresses for internal use only)
importLibraries->size = (sizeof(struct importLibraries) + importLibraries->nameTableSize) - (2 * sizeof(void *));
int ret = ERR_INVALID_IMPORT_NAME;
// Allocate name list
char **names = calloc(importLibraries->tableCount, sizeof(char *));
if(!names)
{ goto cleanup_tables; }
// Populate each table, then compute it's hash and store in the previous table
for(int64_t i = importLibraries->tableCount - 1; i >= 0; i--)
{
// Set the table index field to the current index
importTables[i].tableIndex = i;
// Extract the name, target, and minimum versions from the name string
// Major and minor version are always 2 and 0, respectively
// Strings are definitely null-terminated as otherwise they couldn't have been read in
const uint8_t majorVer = 2;
const uint8_t minorVer = 0;
char *targetBuildVerStr = NULL;
char *targetHotfixVerStr = NULL;
char *minimumBuildVerStr = NULL;
char *minimumHotfixVerStr = NULL;
uint16_t buildVer = 0;
uint8_t hotfixVer = 0;
char *strtoulRet = NULL;
// Get the name
uint32_t oldNameLen = strlen(peImportInfo->tables[i].name);
names[i] = strtok(peImportInfo->tables[i].name, "@");
if(!peImportInfo->tables[i].name)
{ goto cleanup_names_invalid; } // Encountered '\0', not '@'
// Target versions first
targetBuildVerStr = strtok(NULL, ".");
if(!targetBuildVerStr)
{ goto cleanup_names_invalid; }
if(strlen(names[i]) + 1 + strlen(targetBuildVerStr) == oldNameLen)
{ goto cleanup_names_invalid; } // Encountered null terminator instead of '.'
buildVer = (uint16_t)strtoul(targetBuildVerStr, &strtoulRet, 10);
if(*strtoulRet != 0 || strtoulRet == targetBuildVerStr)
{ goto cleanup_names_invalid; } // Encountered a non-number, or string was empty
targetHotfixVerStr = strtok(NULL, "+");
if(!targetHotfixVerStr)
{ goto cleanup_names_invalid; }
if(strlen(names[i]) + 1 + strlen(targetBuildVerStr) + 1 + strlen(targetHotfixVerStr) == oldNameLen)
{ goto cleanup_names_invalid; }
hotfixVer = (uint8_t)strtoul(targetHotfixVerStr, &strtoulRet, 10);
if(*strtoulRet != 0 || strtoulRet == targetHotfixVerStr)
{ goto cleanup_names_invalid; }
// Now pack these into the target version bitfield
importTables[i].targetVer =
((majorVer & 0xF) << 28) |
((minorVer & 0xF) << 24) |
(buildVer << 8) |
hotfixVer;
// Now onto the minimum versions, this works much the same
minimumBuildVerStr = strtok(NULL, ".");
if(!minimumBuildVerStr)
{ goto cleanup_names_invalid; } // No more tokens
if(strlen(names[i]) + 1 + strlen(targetBuildVerStr) + 1 + strlen(targetHotfixVerStr)
+ 1 + strlen(minimumBuildVerStr) == oldNameLen)
{ goto cleanup_names_invalid; } // Encountered null terminator instead of '.'
buildVer = (uint16_t)strtoul(minimumBuildVerStr, &strtoulRet, 10);
if(*strtoulRet != 0 || strtoulRet == minimumBuildVerStr)
{ goto cleanup_names_invalid; } // Encountered a non-number, or string was empty
minimumHotfixVerStr = strtok(NULL, "\0");
if(!minimumHotfixVerStr)
{ goto cleanup_names_invalid; }
hotfixVer = (uint8_t)strtoul(minimumHotfixVerStr, &strtoulRet, 10);
if(*strtoulRet != 0 || strtoulRet == minimumHotfixVerStr)
{ goto cleanup_names_invalid; }
// Now pack these into the minimum version bitfield
importTables[i].minimumVer =
((majorVer & 0xF) << 28) |
((minorVer & 0xF) << 24) |
(buildVer << 8) |
hotfixVer;
// Hardcode a currently unknown value. TODO: find out how this is calculated.
if(strcmp(names[i], "xboxkrnl.exe") == 0)
{ importTables[i].unknown = 0x45DC17E0; }
else if(strcmp(names[i], "xam.xex") == 0)
{ importTables[i].unknown = 0xFCA15C76; }
else if(strcmp(names[i], "xbdm.xex") == 0)
{ importTables[i].unknown = 0xECEB8109; }
else
{ goto cleanup_names_invalid; }
// Determine the number of addresses
importTables[i].addressCount = peImportInfo->tables[i].importCount;
// Allocate enough memory for the addresses
importTables[i].addresses = calloc(importTables[i].addressCount, sizeof(uint32_t));
if(!importTables[i].addresses)
{ goto cleanup_names_invalid; }
uint32_t *addresses = importTables[i].addresses; // Use this to avoid dereferencing an unaligned pointer
uint16_t currentAddr = 0;
// Populate the addresses
for(uint16_t j = 0; j < peImportInfo->tables[i].importCount; j++)
{
if(currentAddr >= importTables[i].addressCount)
{ goto cleanup_names_invalid; }
addresses[currentAddr++] = peImportInfo->tables[i].imports[j].iatAddr;
}
// Determine the total size, in bytes, of the current table (- sizeof(void*) to exclude address to addresses at the end)
importTables[i].size = (sizeof(struct importTable) - sizeof(void *) + (importTables[i].addressCount *sizeof(uint32_t)));
importLibraries->size += importTables[i].size;
// Init sha1 hash
struct sha1_ctx shaContext;
memset(&shaContext, 0, sizeof(shaContext));
sha1_init(&shaContext);
// On little endian this ensures the byteswapped address count doesn't cause any trouble when using it.
// On big endian it's pointless but here so the code isn't too complex with differences between endianness.
uint16_t addressCount = importTables[i].addressCount;
// If we're on a little endian system, swap everything into big endian for hashing
#ifdef LITTLE_ENDIAN_SYSTEM
importTables[i].size = __builtin_bswap32(importTables[i].size);
importTables[i].unknown = __builtin_bswap32(importTables[i].unknown);
importTables[i].targetVer = __builtin_bswap32(importTables[i].targetVer);
importTables[i].minimumVer = __builtin_bswap32(importTables[i].minimumVer);
importTables[i].addressCount = __builtin_bswap16(importTables[i].addressCount);
// Byteswap the addresses
for(uint16_t j = 0; j < addressCount; j++)
{ addresses[j] = __builtin_bswap32(addresses[j]); }
#endif
// - sizeof(void*) to exclude the address to the addresses at the end (not part of the XEX format)
// +/- sizeof(uint32_t) to exclude table size from hash
sha1_update(&shaContext, sizeof(struct importTable) - sizeof(void *) - sizeof(uint32_t), (void *)&importTables[i] + sizeof(uint32_t));
sha1_update(&shaContext, addressCount *sizeof(uint32_t), (void *)addresses);
// If we're on a little endian system, swap everything back into little endian
#ifdef LITTLE_ENDIAN_SYSTEM
importTables[i].size = __builtin_bswap32(importTables[i].size);
importTables[i].unknown = __builtin_bswap32(importTables[i].unknown);
importTables[i].targetVer = __builtin_bswap32(importTables[i].targetVer);
importTables[i].minimumVer = __builtin_bswap32(importTables[i].minimumVer);
importTables[i].addressCount = __builtin_bswap16(importTables[i].addressCount);
// Byteswap the addresses
for(uint16_t j = 0; j < addressCount; j++)
{ addresses[j] = __builtin_bswap32(addresses[j]); }
#endif
sha1_digest(&shaContext, 0x14, i != 0 ? importTables[i - 1].sha1 : secInfoHeader->importTableSha1);
}
// Allocate offset table
uint32_t *nameOffsets = calloc(importLibraries->tableCount, sizeof(uint32_t));
if(!nameOffsets)
{ goto cleanup_names; }
for(uint32_t i = 0; i < importLibraries->tableCount; i++)
{
nameOffsets[i] = importLibraries->nameTableSize;
importLibraries->nameTableSize += getNextAligned(strlen(names[i]) + 1, sizeof(uint32_t));
}
importLibraries->size += importLibraries->nameTableSize;
importLibraries->nameTable = calloc(importLibraries->nameTableSize, sizeof(char));
if(!importLibraries->nameTable)
{ goto cleanup_offsets; }
// Use this to avoid dereferencing an unaligned pointer
char *nameTable = importLibraries->nameTable;
// Populate the name table
for(uint32_t i = 0; i < importLibraries->tableCount; i++)
{ strcpy(&(nameTable[nameOffsets[i]]), names[i]); }
nullAndFree((void **)&nameOffsets);
nullAndFree((void **)&names);
return SUCCESS;
cleanup_offsets:
nullAndFree((void **)&nameOffsets);
cleanup_names:
for(uint32_t i = 0; i < importLibraries->tableCount; i++)
{ nullAndFree((void **) & (importTables[i].addresses)); }
cleanup_names_invalid:
nullAndFree((void **)&names);
cleanup_tables:
nullAndFree((void **) & (importLibraries->importTables));
importLibraries->importTables = NULL;
return ret;
}
// STUB. TODO: Dynamically select, and/or allow user to select, flags
void setSysFlags(uint32_t *flags)
{
if(flags == NULL)
{ return; }
*flags = XEX_SYS_GAMEPAD_DISCONNECT |
XEX_SYS_INSECURE_SOCKETS |
XEX_SYS_XAM_HOOKS |
@ -48,27 +294,61 @@ void setSysFlags(uint32_t *flags)
int setOptHeaders(struct secInfoHeader *secInfoHeader, struct peData *peData, struct optHeaderEntries *optHeaderEntries, struct optHeaders *optHeaders)
{
// TODO: Dynamically select optional headers to use, instead of hard-coding
bool importsPresent = (peData->peImportInfo.totalImportCount > 0) ? true : false;
optHeaderEntries->count = 4;
optHeaderEntries->optHeaderEntry = calloc(5, sizeof(struct optHeaderEntry));
if(optHeaderEntries->optHeaderEntry == NULL) {return ERR_OUT_OF_MEM;}
// First optional header (basefile format)
if(importsPresent)
{ optHeaderEntries->count++; }
optHeaderEntries->optHeaderEntry = calloc(optHeaderEntries->count, sizeof(struct optHeaderEntry));
if(optHeaderEntries->optHeaderEntry == NULL)
{ return ERR_OUT_OF_MEM; }
uint32_t currentHeader = 0;
// NOTE: Make sure that these headers are handled IN ORDER OF ID. The loader will reject the XEX if they are not.
// Basefile format (0x003FF)
setBasefileFormat(&(optHeaders->basefileFormat), secInfoHeader);
optHeaderEntries->optHeaderEntry[0].id = XEX_OPT_ID_BASEFILE_FORMAT;
optHeaderEntries->optHeaderEntry[currentHeader].id = XEX_OPT_ID_BASEFILE_FORMAT;
currentHeader++;
// Second optional header (entrypoint)
optHeaderEntries->optHeaderEntry[1].id = XEX_OPT_ID_ENTRYPOINT;
optHeaderEntries->optHeaderEntry[1].dataOrOffset = secInfoHeader->baseAddr + peData->entryPoint;
// Entrypoint (0x10100)
optHeaderEntries->optHeaderEntry[currentHeader].id = XEX_OPT_ID_ENTRYPOINT;
optHeaderEntries->optHeaderEntry[currentHeader].dataOrOffset = secInfoHeader->baseAddr + peData->entryPoint;
currentHeader++;
// Third optional header (import libs)
//optHeaderEntries->optHeaderEntry[2].id = XEX_OPT_ID_IMPORT_LIBS;
// Import libraries (0x103FF)
if(importsPresent)
{
optHeaderEntries->optHeaderEntry[currentHeader].id = XEX_OPT_ID_IMPORT_LIBS;
int ret = setImportLibsInfo(&(optHeaders->importLibraries), &(peData->peImportInfo), secInfoHeader);
// Fourth optional header (tls info)
optHeaderEntries->optHeaderEntry[2].id = XEX_OPT_ID_TLS_INFO;
setTLSInfo(&(optHeaders->tlsInfo));
if(ret != SUCCESS)
{ return ret; }
// Fifth optional header (system flags)
optHeaderEntries->optHeaderEntry[3].id = XEX_OPT_ID_SYS_FLAGS;
setSysFlags(&(optHeaderEntries->optHeaderEntry[3].dataOrOffset));
currentHeader++;
}
// TLS info (0x20104)
optHeaderEntries->optHeaderEntry[currentHeader].id = XEX_OPT_ID_TLS_INFO;
setTLSInfo(&(optHeaders->tlsInfo));
currentHeader++;
// System flags (0x30000)
optHeaderEntries->optHeaderEntry[currentHeader].id = XEX_OPT_ID_SYS_FLAGS;
// We're using the name of the first element of the struct for clarity,
// it can never be an unaligned access, so ignore that warning.
// Also works for Clang.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Waddress-of-packed-member"
setSysFlags(&(optHeaderEntries->optHeaderEntry[currentHeader].dataOrOffset));
#pragma GCC diagnostic pop
//currentHeader++;
return SUCCESS;
}

View file

@ -19,6 +19,7 @@
#pragma once
#include "../common/common.h"
#include "../common/crypto.h"
#include "../common/datastorage.h"
int setOptHeaders(struct secInfoHeader *secInfoHeader, struct peData *peData, struct optHeaderEntries *optHeaderEntries, struct optHeaders *optHeaders);

View file

@ -23,13 +23,9 @@ uint8_t getRwx(struct secInfoHeader *secInfoHeader, struct peData *peData, uint3
uint32_t pageSize = secInfoHeader->peSize / secInfoHeader->pageDescCount;
uint32_t currentOffset = page *pageSize;
for(int i = peData->sections.count - 1; i >= 0; i--)
{
if(currentOffset >= peData->sections.sectionPerms[i].rva)
{
return peData->sections.sectionPerms[i].permFlag;
}
}
for(int32_t i = peData->sections.count - 1; i >= 0; i--)
if(currentOffset >= peData->sections.section[i].rva)
{ return peData->sections.section[i].permFlag; }
return XEX_SECTION_RODATA | 0b10000; // We're in the PE header, so RODATA
}
@ -37,44 +33,57 @@ uint8_t getRwx(struct secInfoHeader *secInfoHeader, struct peData *peData, uint3
int setPageDescriptors(FILE *pe, struct peData *peData, struct secInfoHeader *secInfoHeader)
{
uint32_t pageSize = secInfoHeader->peSize / secInfoHeader->pageDescCount;
secInfoHeader->descriptors = calloc(secInfoHeader->pageDescCount, sizeof(struct pageDescriptor)); // The free() for this is called after written to XEX
if(secInfoHeader->descriptors == NULL) {return ERR_OUT_OF_MEM;}
secInfoHeader->descriptors = calloc(secInfoHeader->pageDescCount, sizeof(struct pageDescriptor));
if(!secInfoHeader->descriptors)
{ return ERR_OUT_OF_MEM; }
struct pageDescriptor *descriptors = secInfoHeader->descriptors; // So we don't dereference an unaligned pointer
// Setting size/info data and calculating hashes for page descriptors
for(int64_t i = secInfoHeader->pageDescCount - 1; i >= 0; i--)
{
// Get page type (rwx)
secInfoHeader->descriptors[i].sizeAndInfo = getRwx(secInfoHeader, peData, i);
descriptors[i].sizeAndInfo = getRwx(secInfoHeader, peData, i);
// Init sha1 hash
struct sha1_ctx shaContext;
sha1_init(&shaContext);
fseek(pe, i * pageSize, SEEK_SET);
uint8_t page[pageSize];
fread(page, sizeof(uint8_t), pageSize, pe);
if(fseek(pe, i *pageSize, SEEK_SET) != 0)
{ return ERR_FILE_READ; }
uint8_t *page = malloc(pageSize);
if(!page)
{ return ERR_OUT_OF_MEM; }
if(fread(page, 1, pageSize, pe) != pageSize)
{
nullAndFree((void **)&page);
return ERR_FILE_READ;
}
// For little endian systems, swap into big endian for hashing, then back (to keep struct endianness consistent)
#ifdef LITTLE_ENDIAN_SYSTEM
secInfoHeader->descriptors[i].sizeAndInfo = __builtin_bswap32(secInfoHeader->descriptors[i].sizeAndInfo);
descriptors[i].sizeAndInfo = __builtin_bswap32(descriptors[i].sizeAndInfo);
#endif
sha1_update(&shaContext, pageSize, page);
sha1_update(&shaContext, 0x18, (uint8_t*)&secInfoHeader->descriptors[i]);
sha1_update(&shaContext, 0x18, (uint8_t *)&descriptors[i]);
#ifdef LITTLE_ENDIAN_SYSTEM
secInfoHeader->descriptors[i].sizeAndInfo = __builtin_bswap32(secInfoHeader->descriptors[i].sizeAndInfo);
descriptors[i].sizeAndInfo = __builtin_bswap32(descriptors[i].sizeAndInfo);
#endif
if(i != 0)
{
sha1_digest(&shaContext, 0x14, secInfoHeader->descriptors[i - 1].sha1);
}
{ sha1_digest(&shaContext, 0x14, descriptors[i - 1].sha1); }
else
{
sha1_digest(&shaContext, 0x14, secInfoHeader->imageSha1);
}
{ sha1_digest(&shaContext, 0x14, secInfoHeader->imageSha1); }
nullAndFree((void **)&page);
}
free(peData->sections.sectionPerms); // Alloc'd in getdata
return SUCCESS;
}

View file

@ -18,14 +18,15 @@
#include "populateheaders.h"
void setXEXHeader(struct xexHeader *xexHeader, struct peData *peData)
int setXEXHeader(struct xexHeader *xexHeader, struct optHeaderEntries *optHeaderEntries, struct peData *peData)
{
// Writing data into XEX header.
strcpy(xexHeader->magic, "XEX2"); // Magic
strncpy(xexHeader->magic, "XEX2", sizeof(char) * 4); // Magic
// Module flags (type of executable)
xexHeader->moduleFlags = 0;
// Not sure if this is correct, for DLLs specifically the type overrides should probably be used instead
if(xexHeader->moduleFlags == 0)
{
if(peData->characteristics & PE_CHAR_FLAG_DLL)
{
xexHeader->moduleFlags |= XEX_MOD_FLAG_DLL; // The executable is a DLL
@ -39,28 +40,32 @@ void setXEXHeader(struct xexHeader *xexHeader, struct peData *peData)
{
xexHeader->moduleFlags |= XEX_MOD_FLAG_EXPORTS; // The executable exports functions
}
xexHeader->optHeaderCount = 0x4; // Hard-coding until more optional headers supported, then maybe it can be determined dynamically.
}
void setSecInfoHeader(struct secInfoHeader *secInfoHeader, struct peData *peData)
xexHeader->optHeaderCount = optHeaderEntries->count;
return SUCCESS;
}
int setSecInfoHeader(struct secInfoHeader *secInfoHeader, struct peData *peData)
{
// Writing data into security info header (much of this is derived from info in PE)
secInfoHeader->peSize = peData->size;
// Setting signature. Clearing first to ensure no memory contents are written to file, then adding identifier.
memset(secInfoHeader->signature, 0, sizeof(secInfoHeader->signature));
strcpy(secInfoHeader->signature, VERSION_STRING);
// Setting signature (just a SynthXEX version identifier)
strcpy(secInfoHeader->signature, SYNTHXEX_VERSION_STRING);
secInfoHeader->imageInfoSize = 0x174; // Image info size is always 0x174
secInfoHeader->imageFlags = (peData->pageSize == 0x1000 ? XEX_IMG_FLAG_4KIB_PAGES : 0) | XEX_IMG_FLAG_REGION_FREE; // If page size is 4KiB (small pages), set that flag
secInfoHeader->baseAddr = peData->baseAddr;
memset(secInfoHeader->mediaID, 0, sizeof(secInfoHeader->mediaID)); // Null media ID
memset(secInfoHeader->aesKey, 0, sizeof(secInfoHeader->aesKey)); // No encryption, null AES key
//memset(secInfoHeader->mediaID, 0, sizeof(secInfoHeader->mediaID)); // Null media ID (no longer need to use memset, as we use calloc for these structs now)
//memset(secInfoHeader->aesKey, 0, sizeof(secInfoHeader->aesKey)); // No encryption, null AES key
//secInfoHeader->exportTableAddr = TEMPEXPORTADDR;
secInfoHeader->exportTableAddr = 0;
secInfoHeader->gameRegion = XEX_REG_FLAG_REGION_FREE;
secInfoHeader->mediaTypes = 0xFFFFFFFF; // All flags set, can load from any type.
secInfoHeader->pageDescCount = secInfoHeader->peSize / peData->pageSize; // Number of page descriptors following security info (same number of pages)
secInfoHeader->headerSize = (secInfoHeader->pageDescCount *sizeof(struct pageDescriptor)) + (sizeof(struct secInfoHeader) - sizeof(void *)); // Page descriptor total size + length of rest of secinfo header (subtraction of sizeof void* is to remove pointer at end of struct from calculation)
return SUCCESS;
}

View file

@ -21,5 +21,5 @@
#include "../common/common.h"
#include "../common/datastorage.h"
void setXEXHeader(struct xexHeader *xexHeader, struct peData *peData);
void setSecInfoHeader(struct secInfoHeader *secInfoHeader, struct peData *peData);
int setXEXHeader(struct xexHeader *xexHeader, struct optHeaderEntries *optHeaderEntries, struct peData *peData);
int setSecInfoHeader(struct secInfoHeader *secInfoHeader, struct peData *peData);

View file

@ -18,71 +18,94 @@
#include "headerhash.h"
// TEMPORARY, TO BE REPLACED ELSEWHERE WHEN IMPORT HEADER IS IMPLEMENTED
void setImportsSha1(FILE *xex)
{
fseek(xex, 0x0, SEEK_SET);
while(get32BitFromXEX(xex) != 0x103FF){}
fseek(xex, get32BitFromXEX(xex) + 0x4, SEEK_SET);
uint8_t importCount[4];
fseek(xex, 0x4, SEEK_CUR);
fread(importCount, sizeof(uint8_t), 4, xex);
fseek(xex, -0x8, SEEK_CUR);
fseek(xex, get32BitFromXEX(xex) + 0x4, SEEK_CUR);
uint32_t size = get32BitFromXEX(xex) - 0x4;
uint8_t data[size];
fread(data, sizeof(uint8_t), size, xex);
struct sha1_ctx shaContext;
sha1_init(&shaContext);
sha1_update(&shaContext, size, data);
uint8_t sha1[20];
sha1_digest(&shaContext, 20, sha1);
fseek(xex, 0x10, SEEK_SET);
fseek(xex, get32BitFromXEX(xex) + 0x128, SEEK_SET);
fwrite(importCount, sizeof(uint8_t), 4, xex);
fwrite(sha1, sizeof(uint8_t), 20, xex);
}
// So, it would probably be more sensible to endian-swap all of the data back and determine which structure is where
// to determine the hash, but reading the file we just created is easier.
void setHeaderSha1(FILE *xex)
int setHeaderSha1(FILE *xex)
{
fseek(xex, 0x8, SEEK_SET); // Basefile offset
// Get basefile offset
if(fseek(xex, 0x8, SEEK_SET) != 0)
{ return ERR_FILE_READ; }
uint32_t basefileOffset = get32BitFromXEX(xex);
fseek(xex, 0x10, SEEK_SET); // Secinfo offset
if(errno != SUCCESS)
{ return errno; }
// Get secinfo offset
if(fseek(xex, 0x10, SEEK_SET) != 0)
{ return ERR_FILE_READ; }
uint32_t secInfoOffset = get32BitFromXEX(xex);
if(errno != SUCCESS)
{ return errno; }
uint32_t endOfImageInfo = secInfoOffset + 0x8 + 0x174; // 0x8 == image info offset in security info, 0x174 == length of that
uint32_t remainingSize = basefileOffset - endOfImageInfo; // How much data is between end of image info and basefile (we hash that too)
// Init sha1 hash
struct sha1_ctx shaContext;
memset(&shaContext, 0, sizeof(shaContext));
sha1_init(&shaContext);
// Hash first part (remainder of headers is done first, then the start)
uint8_t remainderOfHeaders[remainingSize];
fseek(xex, endOfImageInfo, SEEK_SET);
fread(remainderOfHeaders, sizeof(uint8_t), remainingSize, xex);
uint8_t *remainderOfHeaders = malloc(remainingSize);
if(!remainderOfHeaders)
{ return ERR_OUT_OF_MEM; }
memset(remainderOfHeaders, 0, remainingSize);
if(fseek(xex, endOfImageInfo, SEEK_SET) != 0)
{
nullAndFree((void **)&remainderOfHeaders);
return ERR_FILE_READ;
}
if(fread(remainderOfHeaders, 1, remainingSize, xex) != remainingSize)
{
nullAndFree((void **)&remainderOfHeaders);
return ERR_FILE_READ;
}
sha1_update(&shaContext, remainingSize, remainderOfHeaders);
nullAndFree((void **)&remainderOfHeaders);
uint8_t headersStart[secInfoOffset + 0x8]; // Hash from start up to image info (0x8 into security header)
fseek(xex, 0, SEEK_SET);
// Hash from start up to image info (0x8 into security header)
uint32_t headersLen = secInfoOffset + 0x8;
uint8_t *headersStart = malloc(headersLen);
fread(headersStart, sizeof(uint8_t), secInfoOffset + 0x8, xex);
sha1_update(&shaContext, secInfoOffset + 0x8, headersStart);
if(!headersStart)
{ return ERR_OUT_OF_MEM; }
memset(headersStart, 0, headersLen);
if(fseek(xex, 0, SEEK_SET) != 0)
{
nullAndFree((void **)&headersStart);
return ERR_FILE_READ;
}
if(fread(headersStart, 1, headersLen, xex) != headersLen)
{
nullAndFree((void **)&headersStart);
return ERR_FILE_READ;
}
sha1_update(&shaContext, headersLen, headersStart);
nullAndFree((void **)&headersStart);
// Get final hash
uint8_t headerHash[20];
memset(headerHash, 0, sizeof(headerHash));
sha1_digest(&shaContext, 20, headerHash);
// Finally, write it out
fseek(xex, secInfoOffset + 0x164, SEEK_SET); // 0x164 == offset in secinfo of header hash
fwrite(headerHash, sizeof(uint8_t), 20, xex);
if(fseek(xex, secInfoOffset + 0x164, SEEK_SET) != 0) // 0x164 == offset in secinfo of header hash
{ return ERR_FILE_READ; }
if(fwrite(headerHash, 1, 20, xex) != 20)
{ return ERR_FILE_READ; }
return SUCCESS;
}

View file

@ -1,7 +1,7 @@
// This file is part of SynthXEX, one component of the
// FreeChainXenon development toolchain
//
// Copyright (c) 2024 Aiden Isik
// Copyright (c) 2024-25 Aiden Isik
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@ -22,5 +22,4 @@
#include "../common/crypto.h"
#include "../common/datastorage.h"
void setImportsSha1(FILE *xex);
void setHeaderSha1(FILE *xex);
int setHeaderSha1(FILE *xex);

View file

@ -1,7 +1,7 @@
// This file is part of SynthXEX, one component of the
// FreeChainXenon development toolchain
//
// Copyright (c) 2024 Aiden Isik
// Copyright (c) 2024-25 Aiden Isik
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@ -35,54 +35,66 @@ int writeXEX(struct xexHeader *xexHeader, struct optHeaderEntries *optHeaderEntr
// Optional header entries
#ifdef LITTLE_ENDIAN_SYSTEM
// Endian swap opt header entries
for(uint32_t i = 0; i < optHeaderEntries->count; i++)
{
optHeaderEntries->optHeaderEntry[i].id = __builtin_bswap32(optHeaderEntries->optHeaderEntry[i].id);
optHeaderEntries->optHeaderEntry[i].dataOrOffset = __builtin_bswap32(optHeaderEntries->optHeaderEntry[i].dataOrOffset);
}
#endif
fseek(xex, offsets->optHeaderEntries, SEEK_SET);
for(int i = 0; i < optHeaderEntries->count; i++)
{
fwrite(&(optHeaderEntries->optHeaderEntry[i]), sizeof(uint8_t), sizeof(struct optHeaderEntry), xex);
}
free(optHeaderEntries->optHeaderEntry); // Alloc'd in setdata (optheaders). Now we're done with it.
{ fwrite(&(optHeaderEntries->optHeaderEntry[i]), sizeof(uint8_t), sizeof(struct optHeaderEntry), xex); }
// Page descriptors
fseek(xex, offsets->secInfoHeader + sizeof(struct secInfoHeader) - sizeof(void *), SEEK_SET);
// So we don't try to dereference an unaligned pointer
struct pageDescriptor *descriptors = secInfoHeader->descriptors;
for(int i = 0; i < secInfoHeader->pageDescCount; i++)
{
#ifdef LITTLE_ENDIAN_SYSTEM
secInfoHeader->descriptors[i].sizeAndInfo = __builtin_bswap32(secInfoHeader->descriptors[i].sizeAndInfo);
descriptors[i].sizeAndInfo = __builtin_bswap32(descriptors[i].sizeAndInfo);
#endif
// Writing out current descriptor...
fwrite(&(secInfoHeader->descriptors[i].sizeAndInfo), sizeof(uint32_t), 0x1, xex);
fwrite(secInfoHeader->descriptors[i].sha1, sizeof(uint8_t), 0x14, xex);
fwrite(&(descriptors[i].sizeAndInfo), sizeof(uint32_t), 0x1, xex);
fwrite(descriptors[i].sha1, sizeof(uint8_t), 0x14, xex);
}
free(secInfoHeader->descriptors); // calloc'd elsewhere, freeing now
// Basefile
fseek(pe, 0x0, SEEK_SET);
fseek(xex, offsets->basefile, SEEK_SET);
uint16_t readBufSize = 0x1000; // Reading in 4KiB at a time to avoid excessive memory usage
uint8_t *buffer = malloc(readBufSize *sizeof(uint8_t));
if(buffer == NULL) {return ERR_OUT_OF_MEM;}
if(buffer == NULL)
{ return ERR_OUT_OF_MEM; }
for(uint32_t i = 0; i < secInfoHeader->peSize; i += readBufSize)
{
fread(buffer, sizeof(uint8_t), readBufSize, pe);
fwrite(buffer, sizeof(uint8_t), readBufSize, xex);
size_t bytesToRead = (secInfoHeader->peSize - i < readBufSize)
? secInfoHeader->peSize - i
: readBufSize;
size_t readRet = fread(buffer, sizeof(uint8_t), bytesToRead, pe);
if(readRet != bytesToRead)
{ break; }
size_t writeRet = fwrite(buffer, sizeof(uint8_t), readRet, xex);
if(writeRet != readRet)
{ break; }
}
free(buffer);
nullAndFree((void **)&buffer);
// Security Info
#ifdef LITTLE_ENDIAN_SYSTEM
@ -124,14 +136,55 @@ int writeXEX(struct xexHeader *xexHeader, struct optHeaderEntries *optHeaderEntr
if(optHeaders->importLibraries.size != 0)
{
fseek(xex, offsets->optHeaders[currentHeader], SEEK_SET);
uint32_t importLibsSize = optHeaders->importLibraries.size; // Need to use this value, so save it before we endian-swap
// Write the main header first
// Use these to avoid dereferencing an unaligned pointer
char *nameTable = optHeaders->importLibraries.nameTable;
// Save the values we need to use before byteswapping
uint32_t nameTableSize = optHeaders->importLibraries.nameTableSize;
uint32_t tableCount = optHeaders->importLibraries.tableCount;
#ifdef LITTLE_ENDIAN_SYSTEM
optHeaders->importLibraries.size = __builtin_bswap32(optHeaders->importLibraries.size);
optHeaders->importLibraries.nameTableSize = __builtin_bswap32(optHeaders->importLibraries.nameTableSize);
optHeaders->importLibraries.tableCount = __builtin_bswap32(optHeaders->importLibraries.tableCount);
#endif
fwrite(&(optHeaders->importLibraries.size), sizeof(uint8_t), 0x4, xex);
fwrite(optHeaders->importLibraries.data, sizeof(uint8_t), importLibsSize - 0x4, xex);
fwrite(&(optHeaders->importLibraries), sizeof(uint8_t), sizeof(struct importLibraries) - (2 * sizeof(void *)), xex);
fwrite(nameTable, sizeof(uint8_t), nameTableSize, xex);
#ifdef LITTLE_ENDIAN_SYSTEM
// Restore the table count (we require it to free the import libraries struct later)
optHeaders->importLibraries.tableCount = tableCount;
#endif
// Now write each import table
// Use this to avoid dereferencing an unaligned pointer
struct importTable *importTables = optHeaders->importLibraries.importTables;
for(uint32_t i = 0; i < tableCount; i++)
{
uint32_t *addresses = importTables[i].addresses; // Use this to avoid dereferencing an unaligned pointer
uint16_t addressCount = importTables[i].addressCount;
#ifdef LITTLE_ENDIAN_SYSTEM
importTables[i].size = __builtin_bswap32(importTables[i].size);
importTables[i].unknown = __builtin_bswap32(importTables[i].unknown);
importTables[i].targetVer = __builtin_bswap32(importTables[i].targetVer);
importTables[i].minimumVer = __builtin_bswap32(importTables[i].minimumVer);
importTables[i].addressCount = __builtin_bswap16(importTables[i].addressCount);
for(uint16_t j = 0; j < addressCount; j++)
{ addresses[j] = __builtin_bswap32(addresses[j]); }
#endif
fwrite(&(importTables[i]), sizeof(uint8_t), sizeof(struct importTable) - sizeof(void *), xex);
fwrite(addresses, sizeof(uint32_t), addressCount, xex);
}
currentHeader++;
}
@ -149,6 +202,5 @@ int writeXEX(struct xexHeader *xexHeader, struct optHeaderEntries *optHeaderEntr
fwrite(&(optHeaders->tlsInfo), sizeof(uint8_t), sizeof(struct tlsInfo), xex);
}
free(offsets->optHeaders); // Alloc'd in placer.
return SUCCESS;
}

22
synthxex.nix Normal file
View file

@ -0,0 +1,22 @@
{ stdenv
, cmake
, lib
, ninja
, pkg-config
}:
stdenv.mkDerivation {
name = "synthxex";
allowSubstitutes = false;
src = ./.;
nativeBuildInputs = [ cmake pkg-config ninja ];
postUnpack = ''
chmod -R +w $sourceRoot
'';
installPhase = ''
mkdir -p $out/bin
cp -v synthxex $out/bin/synthxex
'';
}

104
synthxex.scm Normal file
View file

@ -0,0 +1,104 @@
;;; This file is the git repo bundled package definition of SynthXEX for GNU Guix.
;;; To build natively (reproducibly), run "guix time-machine -C channels.scm -- build -f synthxex.scm" in the project root.
;;; To cross compile (reproducibly), run "guix time-machine -C channels.scm -- build -f synthxex.scm --target=<target triplet>" in the project root.
;;; To install, swap "build" for "package" in the above commands
;;;
;;; Copyright (c) 2025 Aiden Isik
;;;
;;; This program is free software: you can redistribute it and/or modify
;;; it under the terms of the GNU Affero General Public License as published by
;;; the Free Software Foundation, either version 3 of the License, or
;;; (at your option) any later version.
;;;
;;; This program 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 Affero General Public License for more details.
;;;
;;; You should have received a copy of the GNU Affero General Public License
;;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Specify the modules we need for this package definition
(define-module (synthxex)
#:use-module (guix gexp)
#:use-module (guix packages)
#:use-module (guix licenses)
#:use-module (guix build-system cmake)
#:use-module (git)
#:use-module (gnu packages version-control)
#:use-module (ice-9 regex))
;;; Hardcoded fallback version in case we can't rely on git
(define synthxex-fallback-version "v0.0.5")
;;; Determine the version of SynthXEX we are building
;;; Returns: version string of SynthXEX being built
(define (get-synthxex-version)
(cond
((< (libgit2-init!) 0)
(display "Warning: failed to initialise libgit2. Using fallback version number.\n")
synthxex-fallback-version)
(else
(let ((synthxex-dir (dirname (current-filename))))
(cond
((eq? (openable-repository? synthxex-dir) #f)
(display "Note: not a git repository. Using fallback version number.\n")
(libgit2-shutdown!)
synthxex-fallback-version)
(else
(let ((synthxex-repo (repository-open synthxex-dir)))
(cond
((repository-empty? synthxex-repo)
(display "Warning: git repository is empty. Using fallback version number.\n")
(libgit2-shutdown!)
synthxex-fallback-version)
((repository-shallow? synthxex-repo)
(display "Warning: git repository is shalllow. Using fallback version number.\n")
(libgit2-shutdown!)
synthxex-fallback-version)
((repository-bare? synthxex-repo)
(display "Warning: git repository is bare. Using fallback version number.\n")
(libgit2-shutdown!)
synthxex-fallback-version)
(else
(let* ((synthxex-desc-opts (make-describe-options #:strategy 'tags)) ; Equivalent to git describe --tags
(synthxex-description (describe-workdir synthxex-repo synthxex-desc-opts))
(synthxex-format-opts (make-describe-format-options #:dirty-suffix "-dirty")) ; Equivalent to git describe --dirty
(synthxex-version-str (describe-format synthxex-description synthxex-format-opts)))
(libgit2-shutdown!) ; Whatever the result of the regex comparison, we're done with git. Clean up here.
(cond ; Check if the version string returned is valid. If it isn't, use the fallback.
((eq? (string-match "^v[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9]+-g[0-9a-f]+(-dirty)?)?$" synthxex-version-str) #f)
(display "Warning: version string is invalid. Using fallback version number.\n")
synthxex-fallback-version)
(else
synthxex-version-str))))))))))))
;;; Define the SynthXEX package
(define-public synthxex
(package
(name "synthxex")
(version (get-synthxex-version))
(source (local-file (dirname (current-filename)) #:recursive? #t))
(build-system cmake-build-system)
(arguments
'(#:build-type "Debug" ; This is the version of the package definition bundled in the git repo, so we should build a debug version
#:tests? #f)) ; We don't have a test suite
(native-inputs
(list git)) ; Needed for version number generation by cmake
(synopsis "XEX2 generator for the Xbox 360 games console")
(description "SynthXEX is an XEX2 generator, the final component of development toolchains for the Xbox 360 games console.")
(home-page "https://git.aidenisik.scot/FreeChainXenon/SynthXEX")
(license agpl3+)))
synthxex