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 # 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/>. # 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.24.2)
cmake_minimum_required(VERSION 3.25.1)
project(synthxex) project(synthxex)
# Make sure we're not being compiled with MSVC (we don't support it)
if(MSVC) 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\" ..).") 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() return()
endif() endif()
# Setting sources... # Gather all source and header files
set(allsources ${CMAKE_SOURCE_DIR}/src/main.c) 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) # Setting compilation settings
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...
add_executable(synthxex ${allsources}) add_executable(synthxex ${allsources})
target_include_directories(synthxex PRIVATE ${CMAKE_SOURCE_DIR}/include) target_include_directories(synthxex PRIVATE ${CMAKE_SOURCE_DIR}/include)
# If we're doing a debug build, compile with debugging and git commit hash # Debug/release build handling
if(NOT ("${CMAKE_BUILD_TYPE}" STREQUAL "Release")) 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(${SYNTHXEX_BUILD_TYPE} MATCHES "Deb")
if(NOT (MINGW)) add_compile_definitions(_DEBUG=1)
target_compile_options(synthxex PRIVATE -O0 -g -fsanitize=address)
target_link_options(synthxex PRIVATE -fsanitize=address)
else()
target_compile_options(synthxex PRIVATE -O0 -g)
endif()
execute_process( if(NOT MINGW)
COMMAND git rev-parse --short HEAD target_compile_options(synthxex PRIVATE -O0 -g -fsanitize=address -fsanitize=undefined)
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} target_link_options(synthxex PRIVATE -lasan -lubsan -fsanitize=address -fsanitize=undefined)
OUTPUT_VARIABLE GIT_COMMIT else()
OUTPUT_STRIP_TRAILING_WHITESPACE target_compile_options(synthxex PRIVATE -O0 -g)
) endif()
endif()
add_compile_definitions(GIT_COMMIT="${GIT_COMMIT}") # Set the version
execute_process(
COMMAND git describe --tags --dirty
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE VERSION_STRING
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# 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() endif()
# Setting install target settings... # 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 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. 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 (POSIX, standard method)
## Building
### Prerequisites ### Prerequisites
- A C compiler (GCC/Clang) - A C compiler (GCC/Clang)
- CMake (>= 3.25.1) - CMake (>= 3.24.2)
- Make - Make
- Git - Git
@ -31,7 +28,7 @@ To install these on Debian: ```sudo apt install gcc cmake make git```
### Downloading ### Downloading
Run: ```git clone https://git.aidenisik.scot/FreeChainXenon/SynthXEX``` Clone: ```git clone https://git.aidenisik.scot/FreeChainXenon/SynthXEX```
### Compiling ### Compiling
@ -45,15 +42,102 @@ Build: ```make```
Install: ```sudo make install``` 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: ## Special thanks to:
- [InvoxiPlayGames](https://github.com/InvoxiPlayGames), for help understanding the XEX format - [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 - [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 - [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 ## Licensing
### SynthXEX (src/*, CMakeLists.txt) ### SynthXEX
Copyright (c) 2024-25 Aiden Isik 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 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/>. 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> 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 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
### GNU Nettle (include/nettle/*) ### GNU Nettle
Copyright (C) 2001, 2013 Niels Möller 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 #pragma once
#include <stdio.h> #include <stdio.h>
#include <errno.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
// Program identifiers // Forbid the use of the free function
#define NAME "SynthXEX" #define freeOnlyUseThisFunctionInTheNullAndFreeFunctionNowhereElse free
#define VERSION "v0.0.3" #pragma GCC poison free
#define COPYRIGHT "2024-25"
#ifdef GIT_COMMIT // Program identifiers (version set in CMakeLists.txt)
#define VERSION_STRING NAME " " VERSION "-dev-" GIT_COMMIT #if defined(DEBUG) || defined(_DEBUG)
#define SYNTHXEX_NAME "SynthXEX-Debug"
#else #else
#define VERSION_STRING NAME " " VERSION #define SYNTHXEX_NAME "SynthXEX"
#endif #endif
#define SYNTHXEX_COPYRIGHT "2024-2025"
#define SYNTHXEX_VERSION_STRING SYNTHXEX_NAME " " SYNTHXEX_VERSION
// Print constants // Print constants
#define PRINT_STEM "SynthXEX>" #define SYNTHXEX_PRINT_STEM SYNTHXEX_NAME ">"
// Return values // Return values
#define SUCCESS 0 #define SUCCESS 0
@ -45,7 +49,9 @@
#define ERR_MISSING_SECTION_FLAG -2 #define ERR_MISSING_SECTION_FLAG -2
#define ERR_FILE_OPEN -3 #define ERR_FILE_OPEN -3
#define ERR_FILE_READ -4 #define ERR_FILE_READ -4
#define ERR_OUT_OF_MEM -5 #define ERR_FILE_WRITE -5
#define ERR_UNSUPPORTED_STRUCTURE -6 #define ERR_OUT_OF_MEM -6
#define ERR_UNSUPPORTED_STRUCTURE -7
void infoPrint(char *str); #define ERR_INVALID_RVA_OR_OFFSET -8
#define ERR_INVALID_IMPORT_NAME -9
#define ERR_DATA_OVERFLOW -10

View file

@ -18,94 +18,260 @@
#include "datastorage.h" #include "datastorage.h"
uint32_t getNextAligned(uint32_t offset, uint32_t alignment) // 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(offset % alignment) // If offset not aligned if(ptr != NULL)
{ {
return offset + (alignment - (offset % alignment)); // Align if(*ptr != NULL)
{
freeOnlyUseThisFunctionInTheNullAndFreeFunctionNowhereElse(*ptr);
*ptr = NULL;
}
} }
return offset; // Offset already aligned
} }
// TODO: Combine all of these into a single function // 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
{
return offset + (alignment - (offset % alignment)); // Align
}
return offset; // Offset already aligned
}
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) uint32_t get32BitFromPE(FILE *pe)
{ {
uint8_t store[4]; uint32_t result;
fread(store, sizeof(uint8_t), 4, pe); 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) // If system is big endian, swap endianness (PE is LE)
#ifdef BIG_ENDIAN_SYSTEM #ifdef BIG_ENDIAN_SYSTEM
result = __builtin_bswap32(result); return __builtin_bswap32(result);
#else
return result;
#endif #endif
return result;
} }
uint16_t get16BitFromPE(FILE *pe) uint16_t get16BitFromPE(FILE *pe)
{ {
uint8_t store[2]; uint16_t result;
fread(store, sizeof(uint8_t), 2, pe); 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 #ifdef BIG_ENDIAN_SYSTEM
result = htons(result); return __builtin_bswap16(result);
#else
return result;
#endif #endif
return result;
} }
uint32_t get32BitFromXEX(FILE *xex) uint32_t get32BitFromXEX(FILE *xex)
{ {
uint8_t store[4]; uint32_t result;
fread(store, sizeof(uint8_t), 4, xex); 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 #ifdef LITTLE_ENDIAN_SYSTEM
result = __builtin_bswap32(result); return __builtin_bswap32(result);
#else
return result;
#endif #endif
return result;
} }
uint16_t get16BitFromXEX(FILE *xex) uint16_t get16BitFromXEX(FILE *xex)
{ {
uint8_t store[2]; uint16_t result;
fread(store, sizeof(uint8_t), 2, xex); 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 #ifdef LITTLE_ENDIAN_SYSTEM
result = __builtin_bswap16(result); return __builtin_bswap16(result);
#else
return result;
#endif #endif
return result;
} }

View file

@ -22,11 +22,11 @@
// Endian test // Endian test
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define LITTLE_ENDIAN_SYSTEM #define LITTLE_ENDIAN_SYSTEM
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define BIG_ENDIAN_SYSTEM #define BIG_ENDIAN_SYSTEM
#else #else
#error "System is not big or little endian! SynthXEX does not support this archaic dinosaur, sorry!" #error "System is not big or little endian! SynthXEX does not support this archaic dinosaur, sorry!"
#endif #endif
// Preprocessor definitions // Preprocessor definitions
@ -54,7 +54,7 @@
#define XEX_OPT_ID_TLS_INFO 0x20104 #define XEX_OPT_ID_TLS_INFO 0x20104
#define XEX_OPT_ID_SYS_FLAGS 0x30000 #define XEX_OPT_ID_SYS_FLAGS 0x30000
// System flags // System flags
#define XEX_SYS_GAMEPAD_DISCONNECT 0x00000020 #define XEX_SYS_GAMEPAD_DISCONNECT 0x00000020
#define XEX_SYS_INSECURE_SOCKETS 0x00000040 #define XEX_SYS_INSECURE_SOCKETS 0x00000040
#define XEX_SYS_XAM_HOOKS 0x00001000 #define XEX_SYS_XAM_HOOKS 0x00001000
@ -67,6 +67,9 @@
#define PE_SECTION_FLAG_READ 0x40000000 #define PE_SECTION_FLAG_READ 0x40000000
#define PE_SECTION_FLAG_WRITE 0x80000000 #define PE_SECTION_FLAG_WRITE 0x80000000
// PE import ordinal flag
#define PE_IMPORT_ORDINAL_FLAG 0x80000000
// RWX flags (XEX) // RWX flags (XEX)
#define XEX_SECTION_CODE 0x1 #define XEX_SECTION_CODE 0x1
#define XEX_SECTION_RWDATA 0x2 #define XEX_SECTION_RWDATA 0x2
@ -75,142 +78,201 @@
// Page RWX flags // Page RWX flags
struct sections struct sections
{ {
uint16_t count; uint16_t count;
struct sectionPerms *sectionPerms; struct section *section;
}; };
struct sectionPerms struct section
{ {
uint8_t permFlag; uint8_t permFlag;
uint32_t rva; 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 struct peImportInfo
{ {
uint16_t count; uint32_t idtRVA;
uint32_t tableCount;
uint32_t totalImportCount;
struct peImportTable *tables;
}; };
struct peExportInfo struct peExportInfo
{ {
uint16_t count; uint16_t count;
}; };
// Data structs // Data structs
struct peData struct peData
{ {
uint32_t size; uint32_t size;
uint32_t baseAddr; uint32_t baseAddr;
uint32_t entryPoint; uint32_t entryPoint;
uint32_t tlsAddr; uint32_t tlsAddr;
uint32_t tlsSize; uint32_t tlsSize;
uint32_t peHeaderOffset; uint32_t peHeaderOffset;
uint16_t numberOfSections; uint16_t numberOfSections;
uint32_t sectionTableSize; uint32_t sectionTableSize;
uint32_t headerSize; uint32_t headerSize;
uint32_t pageSize; uint32_t pageSize;
uint16_t characteristics; uint16_t characteristics;
struct sections sections; struct sections sections;
struct peImportInfo peImportInfo; struct peImportInfo peImportInfo;
struct peExportInfo peExportInfo; struct peExportInfo peExportInfo;
}; };
// Most of these are 8-byte aligned. Basefile is 4KiB aligned. // Most of these are 8-byte aligned. Basefile is 4KiB aligned.
// In order of appearance // In order of appearance
struct offsets struct offsets
{ {
uint32_t xexHeader; uint32_t xexHeader;
uint32_t optHeaderEntries; uint32_t optHeaderEntries;
uint32_t secInfoHeader; uint32_t secInfoHeader;
uint32_t *optHeaders; uint32_t *optHeaders;
uint32_t basefile; uint32_t basefile;
}; };
struct __attribute__((packed)) xexHeader struct __attribute__((packed)) xexHeader
{ {
char magic[4]; char magic[4];
uint32_t moduleFlags; uint32_t moduleFlags;
uint32_t peOffset; uint32_t peOffset;
uint32_t reserved; uint32_t reserved;
uint32_t secInfoOffset; uint32_t secInfoOffset;
uint32_t optHeaderCount; uint32_t optHeaderCount;
}; };
struct __attribute__((packed)) pageDescriptor struct __attribute__((packed)) pageDescriptor
{ {
uint32_t sizeAndInfo; // First 28 bits == size, last 4 == info (RO/RW/X) uint32_t sizeAndInfo; // First 28 bits == size, last 4 == info (RO/RW/X)
uint8_t sha1[0x14]; uint8_t sha1[0x14];
}; };
struct __attribute__((packed)) secInfoHeader struct __attribute__((packed)) secInfoHeader
{ {
uint32_t headerSize; uint32_t headerSize;
uint32_t peSize; uint32_t peSize;
// - IMAGE INFO - // - IMAGE INFO -
uint8_t signature[0x100]; uint8_t signature[0x100];
uint32_t imageInfoSize; uint32_t imageInfoSize;
uint32_t imageFlags; uint32_t imageFlags;
uint32_t baseAddr; uint32_t baseAddr;
uint8_t imageSha1[0x14]; uint8_t imageSha1[0x14];
uint32_t importTableCount; uint32_t importTableCount;
uint8_t importTableSha1[0x14]; uint8_t importTableSha1[0x14];
uint8_t mediaID[0x10]; uint8_t mediaID[0x10];
uint8_t aesKey[0x10]; uint8_t aesKey[0x10];
uint32_t exportTableAddr; uint32_t exportTableAddr;
uint8_t headersHash[0x14]; uint8_t headersHash[0x14];
uint32_t gameRegion; uint32_t gameRegion;
// - IMAGE INFO - // - IMAGE INFO -
uint32_t mediaTypes; uint32_t mediaTypes;
uint32_t pageDescCount; uint32_t pageDescCount;
struct pageDescriptor *descriptors; struct pageDescriptor *descriptors;
}; };
struct __attribute__((packed)) basefileFormat struct __attribute__((packed)) basefileFormat
{ {
uint32_t size; uint32_t size;
uint16_t encType; uint16_t encType;
uint16_t compType; uint16_t compType;
uint32_t dataSize; uint32_t dataSize;
uint32_t zeroSize; 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 struct __attribute__((packed)) importLibraries
{ {
uint32_t size; uint32_t size;
uint8_t *data; uint32_t nameTableSize;
uint32_t tableCount;
char *nameTable;
struct importTable *importTables;
}; };
struct __attribute__((packed)) tlsInfo struct __attribute__((packed)) tlsInfo
{ {
uint32_t slotCount; uint32_t slotCount;
uint32_t rawDataAddr; uint32_t rawDataAddr;
uint32_t dataSize; uint32_t dataSize;
uint32_t rawDataSize; uint32_t rawDataSize;
}; };
struct optHeaderEntries struct optHeaderEntries
{ {
uint32_t count; uint32_t count;
struct optHeaderEntry *optHeaderEntry; struct optHeaderEntry *optHeaderEntry;
}; };
struct __attribute__((packed)) optHeaderEntry struct __attribute__((packed)) optHeaderEntry
{ {
uint32_t id; uint32_t id;
uint32_t dataOrOffset; uint32_t dataOrOffset;
}; };
struct optHeaders struct optHeaders
{ {
struct basefileFormat basefileFormat; struct basefileFormat basefileFormat;
struct importLibraries importLibraries; struct importLibraries importLibraries;
struct tlsInfo tlsInfo; 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 // Functions used for file data manipulation
uint32_t getNextAligned(uint32_t offset, uint32_t alignment); 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); uint32_t get32BitFromPE(FILE *pe);
uint16_t get16BitFromPE(FILE *pe); uint16_t get16BitFromPE(FILE *pe);
uint32_t get32BitFromXEX(FILE *xex); 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 // This file is part of SynthXEX, one component of the
// FreeChainXenon development toolchain // 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 // 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 // it under the terms of the GNU Affero General Public License as published by
@ -18,7 +18,6 @@
#pragma once #pragma once
#include <sys/stat.h>
#include "../common/common.h" #include "../common/common.h"
#include "../common/datastorage.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 // 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/>. // 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/common.h"
#include "common/datastorage.h" #include "common/datastorage.h"
#include "pemapper/pemapper.h" #include "pemapper/pemapper.h"
#include "getdata/getdata.h" #include "getdata/gethdrdata.h"
#include "getdata/getimports.h"
#include "setdata/populateheaders.h" #include "setdata/populateheaders.h"
#include "setdata/pagedescriptors.h" #include "setdata/pagedescriptors.h"
#include "setdata/optheaders.h" #include "setdata/optheaders.h"
@ -32,382 +36,499 @@
void dispLibs() void dispLibs()
{ {
printf("\nLibraries utilised by SynthXEX:\n\n"); printf("\nLibraries utilised by SynthXEX:\n\n");
printf("----- GETOPT_PORT -----\n\n"); printf("----- GETOPT_PORT -----\n\n");
printf("Copyright (c) 2012-2023, Kim Grasman <kim.grasman@gmail.com>\n"); printf("Copyright (c) 2012-2023, Kim Grasman <kim.grasman@gmail.com>\n");
printf("All rights reserved.\n\n"); printf("All rights reserved.\n\n");
printf("Redistribution and use in source and binary forms, with or without\n"); printf("Redistribution and use in source and binary forms, with or without\n");
printf("modification, are permitted provided that the following conditions are met:\n"); printf("modification, are permitted provided that the following conditions are met:\n");
printf("* Redistributions of source code must retain the above copyright\n"); printf("* Redistributions of source code must retain the above copyright\n");
printf(" notice, this list of conditions and the following disclaimer.\n"); printf(" notice, this list of conditions and the following disclaimer.\n");
printf("* Redistributions in binary form must reproduce the above copyright\n"); printf("* Redistributions in binary form must reproduce the above copyright\n");
printf(" notice, this list of conditions and the following disclaimer in the\n"); printf(" notice, this list of conditions and the following disclaimer in the\n");
printf(" documentation and/or other materials provided with the distribution.\n"); printf(" documentation and/or other materials provided with the distribution.\n");
printf("* Neither the name of Kim Grasman nor the\n"); printf("* Neither the name of Kim Grasman nor the\n");
printf(" names of contributors may be used to endorse or promote products\n"); printf(" names of contributors may be used to endorse or promote products\n");
printf(" derived from this software without specific prior written permission.\n\n"); printf(" derived from this software without specific prior written permission.\n\n");
printf("THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n"); printf("THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n");
printf("ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n"); printf("ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n");
printf("WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n"); printf("WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n");
printf("DISCLAIMED. IN NO EVENT SHALL KIM GRASMAN BE LIABLE FOR ANY\n"); printf("DISCLAIMED. IN NO EVENT SHALL KIM GRASMAN BE LIABLE FOR ANY\n");
printf("DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n"); printf("DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n");
printf("(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n"); printf("(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n");
printf("LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n"); printf("LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n");
printf("ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"); printf("ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n");
printf("(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n"); printf("(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n");
printf("SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n"); printf("SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n");
printf("----- GNU NETTLE (SHA-1) -----\n\n"); printf("----- GNU NETTLE (SHA-1) -----\n\n");
printf("Copyright (C) 2001, 2013 Niels Möller\n"); printf("Copyright (C) 2001, 2013 Niels Möller\n");
printf("This file is part of GNU Nettle.\n\n"); printf("This file is part of GNU Nettle.\n\n");
printf("GNU Nettle is free software: you can redistribute it and/or\n"); printf("GNU Nettle is free software: you can redistribute it and/or\n");
printf("modify it under the terms of either:\n\n"); printf("modify it under the terms of either:\n\n");
printf("* the GNU Lesser General Public License as published by the Free\n"); printf("* the GNU Lesser General Public License as published by the Free\n");
printf(" Software Foundation; either version 3 of the License, or (at your\n"); printf(" Software Foundation; either version 3 of the License, or (at your\n");
printf(" option) any later version.\n\n"); printf(" option) any later version.\n\n");
printf("or\n\n"); printf("or\n\n");
printf("* the GNU General Public License as published by the Free\n"); printf("* the GNU General Public License as published by the Free\n");
printf(" Software Foundation; either version 2 of the License, or (at your\n"); printf(" Software Foundation; either version 2 of the License, or (at your\n");
printf(" option) any later version.\n\n"); printf(" option) any later version.\n\n");
printf("or both in parallel, as here.\n\n"); printf("or both in parallel, as here.\n\n");
printf("GNU Nettle is distributed in the hope that it will be useful,\n"); printf("GNU Nettle is distributed in the hope that it will be useful,\n");
printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n"); printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"); printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n");
printf("General Public License for more details.\n\n"); printf("General Public License for more details.\n\n");
printf("You should have received copies of the GNU General Public License and\n"); printf("You should have received copies of the GNU General Public License and\n");
printf("the GNU Lesser General Public License along with this program. If\n"); printf("the GNU Lesser General Public License along with this program. If\n");
printf("not, see http://www.gnu.org/licenses/.\n\n"); printf("not, see http://www.gnu.org/licenses/.\n\n");
} }
void dispVer() void dispVer()
{ {
printf("\n%s\n\nThe XEX builder of the FreeChainXenon project\n\n", VERSION_STRING); 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", COPYRIGHT); 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("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("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"); printf("(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\n");
printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"); printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n");
printf("GNU Affero General Public License for more details.\n\n"); printf("GNU Affero General Public License for more details.\n\n");
printf("You should have received a copy of the GNU Affero General Public License\n"); printf("You should have received a copy of the GNU Affero General Public License\n");
printf("along with this program. If not, see <https://www.gnu.org/licenses/>.\n\n"); printf("along with this program. If not, see <https://www.gnu.org/licenses/>.\n\n");
} }
void dispHelp(char **argv) void dispHelp(char **argv)
{ {
printf("\nUsage: %s [OPTION] <ARG>\n\n", argv[0]); printf("\nUsage: %s [OPTION] <ARG>\n\n", argv[0]);
printf("Options:\n"); printf("Options:\n");
printf("-h,\t--help,\t\t\tShow this information\n"); printf("-h,\t--help,\t\t\tShow this information\n");
printf("-v,\t--version,\t\tShow version and licensing information\n"); printf("-v,\t--version,\t\tShow version and licensing information\n");
printf("-l,\t--libs,\t\t\tShow licensing information of libraries used\n"); 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("-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("-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) int main(int argc, char **argv)
{ {
static struct option longOptions[] = 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},
{0, 0, 0, 0}
};
int optIndex = 0;
int option = 0;
bool gotInput = false;
bool gotOutput = false;
bool skipMachineCheck = false;
char *pePath = NULL;
char *xexfilePath = NULL;
while((option = getopt_long(argc, argv, "hvlsi:o:", 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))
{
dispVer();
return SUCCESS;
}
else if(option == 'l' || (option == 0 && strcmp(longOptions[optIndex].name, "libs") == 0))
{
dispLibs();
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);
skipMachineCheck = true;
}
else if(option == 'i' || (option == 0 && strcmp(longOptions[optIndex].name, "input") == 0))
{
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);}
return -1;
}
strncpy(pePath, optarg, strlen(optarg) + 1);
}
else if(option == 'o' || (option == 0 && strcmp(longOptions[optIndex].name, "output") == 0))
{
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);}
return -1;
}
strncpy(xexfilePath, optarg, strlen(optarg) + 1);
}
}
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]);
// Check we got everything we need
if(!gotInput)
{ {
if(gotOutput) { "help", no_argument, 0, 'h' },
{ { "version", no_argument, 0, 'v' },
free(xexfilePath); { "libs", no_argument, 0, 'l' },
} { "skip-machine-check", no_argument, 0, 's' },
{ "input", required_argument, 0, 'i' },
printf("%s ERROR: PE input expected but not found. Aborting.\n", PRINT_STEM); { "output", required_argument, 0, 'o' },
return -1; { "type", required_argument, 0, 't' },
} { 0, 0, 0, 0 }
else if(!gotOutput) };
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)
{ {
if(gotInput) printf("%s ERROR: Out of memory. Aborting\n", SYNTHXEX_PRINT_STEM);
{ freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData,
free(pePath); &optHeaderEntries, &optHeaders);
} return -1;
printf("%s ERROR: XEX file output expected but not found. Aborting.\n", PRINT_STEM);
return -1;
} }
// Opening the files now that they've been validated int optIndex = 0;
FILE *pe = fopen(pePath, "rb"); int option = 0;
if(pe == NULL) bool gotInput = false;
bool gotOutput = false;
bool skipMachineCheck = false;
char *pePath = NULL;
char *xexfilePath = NULL;
while((option = getopt_long(argc, argv, "hvlsi:o:t:", longOptions, &optIndex)) != -1)
{ {
printf("%s ERROR: Failed to open PE file. Do you have read permissions? Aborting.\n", PRINT_STEM); switch(option)
free(pePath); {
free(xexfilePath); case 'v':
return -1; dispVer();
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData,
&optHeaderEntries, &optHeaders);
return SUCCESS;
case 'l':
dispLibs();
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData,
&optHeaderEntries, &optHeaders);
return SUCCESS;
case 's':
printf("%s WARNING: Skipping machine ID check.\n", SYNTHXEX_PRINT_STEM);
skipMachineCheck = true;
break;
case 'i':
gotInput = true;
pePath = malloc(strlen(optarg) + 1);
if(pePath == NULL)
{
printf("%s ERROR: Out of memory. Aborting.\n", SYNTHXEX_PRINT_STEM);
nullAndFree((void **)&xexfilePath);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData,
&optHeaderEntries, &optHeaders);
return -1;
}
strcpy(pePath, optarg);
break;
case 'o':
gotOutput = true;
xexfilePath = malloc(strlen(optarg) + 1);
if(xexfilePath == NULL)
{
printf("%s ERROR: Out of memory. Aborting.\n", SYNTHXEX_PRINT_STEM);
nullAndFree((void **)&pePath);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData,
&optHeaderEntries, &optHeaders);
return -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;
}
} }
free(pePath); 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]);
FILE *xex = fopen(xexfilePath, "wb+");
if(xex == NULL) if(!gotInput)
{ {
printf("%s ERROR: Failed to create XEX file. Do you have write permissions? Aborting.\n", PRINT_STEM); if(gotOutput)
fclose(pe); { nullAndFree((void **)&xexfilePath); }
free(xexfilePath);
return -1; 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)
// 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);
int ret;
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);
if(!validatePE(pe, skipMachineCheck))
{ {
printf("%s ERROR: Input PE is not Xbox 360 PE. Aborting.\n", PRINT_STEM); if(gotInput)
free(xexfilePath); { nullAndFree((void **)&pePath); }
fclose(pe);
fclose(xex); freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
return -1; printf("%s ERROR: XEX file output expected but not found. Aborting.\n", SYNTHXEX_PRINT_STEM);
return -1;
} }
printf("%s PE valid!\n", PRINT_STEM); FILE *pe = fopen(pePath, "rb");
// Reading in header data from PE if(pe == NULL)
printf("%s Retrieving header data from PE...\n", PRINT_STEM);
ret = getHdrData(pe, &peData, 0);
if(ret == ERR_UNKNOWN_DATA_REQUEST)
{ {
printf("%s ERROR: Internal error getting data from PE file. THIS IS A BUG, please report it. Aborting.\n", PRINT_STEM); printf("%s ERROR: Failed to open PE file. Do you have read permissions? Aborting.\n", SYNTHXEX_PRINT_STEM);
fclose(pe); nullAndFree((void **)&pePath);
fclose(xex); nullAndFree((void **)&xexfilePath);
return -1; freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
} 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);
fclose(pe);
fclose(xex);
return -1;
} }
printf("%s Got header data from PE!\n", PRINT_STEM); nullAndFree((void **)&pePath);
// Determine the path we should save the basefile at and open it.
printf("%s Creating basefile from PE...\n", PRINT_STEM);
char *basefilePath = malloc((strlen(xexfilePath) + strlen(".basefile") + 1) * sizeof(char));
if(basefilePath == NULL) FILE *xex = fopen(xexfilePath, "wb+");
if(xex == NULL)
{ {
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM); printf("%s ERROR: Failed to create XEX file. Do you have write permissions? Aborting.\n", SYNTHXEX_PRINT_STEM);
free(xexfilePath); fclose(pe);
fclose(pe); nullAndFree((void **)&xexfilePath);
fclose(xex); freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
return -1; return -1;
} }
strcpy(basefilePath, xexfilePath); // Keep xexfilePath for later use
strcat(basefilePath, ".basefile"); // Do NOT free xexfilePath here yet
FILE* basefile = fopen(basefilePath, "wb+"); int ret = 0;
free(xexfilePath); // *Now* we're done with it.
free(basefilePath); printf("%s Validating PE file...\n", SYNTHXEX_PRINT_STEM);
if(basefile == NULL) if(!validatePE(pe, skipMachineCheck))
{ {
printf("%s ERROR: Could not create basefile. Do you have write permission? Aborting.\n", PRINT_STEM); printf("%s ERROR: Input PE is not Xbox 360 PE. Aborting.\n", SYNTHXEX_PRINT_STEM);
fclose(pe); nullAndFree((void **)&xexfilePath);
fclose(xex); freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
return -1; fclose(pe);
fclose(xex);
return -1;
} }
// Map the PE into the basefile (RVAs become offsets) printf("%s PE valid!\n", SYNTHXEX_PRINT_STEM);
ret = mapPEToBasefile(pe, basefile, &peData);
fclose(pe);
if(ret == ERR_OUT_OF_MEM) printf("%s Retrieving header data from PE...\n", SYNTHXEX_PRINT_STEM);
{ ret = getHdrData(pe, peData, 0);
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
fclose(basefile);
fclose(xex);
return -1;
}
printf("%s Created basefile!\n", PRINT_STEM);
// Setting final XEX data structs
printf("%s Building XEX header...\n", PRINT_STEM);
setXEXHeader(&xexHeader, &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);
fclose(basefile); nullAndFree((void **)&xexfilePath);
fclose(xex); freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
return -1; fclose(pe);
} fclose(xex);
return -1;
printf("%s Building optional headers...\n", PRINT_STEM);
ret = setOptHeaders(&secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
if(ret == ERR_OUT_OF_MEM)
{
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
fclose(basefile);
fclose(xex);
return -1;
}
// Setting data positions...
printf("%s Aligning data...\n", PRINT_STEM);
ret = placeStructs(&offsets, &xexHeader, &optHeaderEntries, &secInfoHeader, &optHeaders);
if(ret == ERR_OUT_OF_MEM)
{
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
fclose(basefile);
fclose(xex);
return -1;
}
// 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);
if(ret == ERR_OUT_OF_MEM)
{
printf("%s ERROR: Out of memory. Aborting.\n", PRINT_STEM);
fclose(basefile);
fclose(xex);
return -1;
} }
printf("%s Main data written to XEX file!\n", PRINT_STEM); printf("%s Got header data from PE!\n", SYNTHXEX_PRINT_STEM);
// Final pass (sets & writes header hash) printf("%s Retrieving import data from PE...\n", SYNTHXEX_PRINT_STEM);
printf("%s Calculating and writing header SHA1...\n", PRINT_STEM); ret = getImports(pe, peData);
setHeaderSha1(xex);
printf("%s Header SHA1 written!\n", PRINT_STEM); if(ret != SUCCESS)
{
fclose(basefile); handleError(ret);
fclose(xex); nullAndFree((void **)&xexfilePath);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
printf("%s XEX built. Have a nice day!\n\n", PRINT_STEM); fclose(pe);
return SUCCESS; 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)
{
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;
}
strcpy(basefilePath, xexfilePath);
strcat(basefilePath, ".basefile");
FILE *basefile = fopen(basefilePath, "wb+");
nullAndFree((void **)&xexfilePath);
nullAndFree((void **)&basefilePath);
if(!basefile)
{
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);
fclose(pe);
if(ret != SUCCESS)
{
handleError(ret);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(basefile);
fclose(xex);
return -1;
}
printf("%s Created basefile!\n", SYNTHXEX_PRINT_STEM);
// Setting final XEX data structs
printf("%s Building security header...\n", SYNTHXEX_PRINT_STEM);
ret = setSecInfoHeader(secInfoHeader, peData);
if(ret != SUCCESS)
{
handleError(ret);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(basefile);
fclose(xex);
return -1;
}
printf("%s Setting page descriptors...\n", SYNTHXEX_PRINT_STEM);
ret = setPageDescriptors(basefile, peData, secInfoHeader);
if(ret != SUCCESS)
{
handleError(ret);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(basefile);
fclose(xex);
return -1;
}
// Done with this now
freeSectionsStruct(&(peData->sections));
printf("%s Building optional headers...\n", SYNTHXEX_PRINT_STEM);
ret = setOptHeaders(secInfoHeader, peData, optHeaderEntries, optHeaders);
if(ret != SUCCESS)
{
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 XEX...\n", SYNTHXEX_PRINT_STEM);
ret = writeXEX(xexHeader, optHeaderEntries, secInfoHeader, optHeaders, offsets, basefile, xex);
if(ret != SUCCESS)
{
handleError(ret);
freeAllMainStructs(&offsets, &xexHeader, &secInfoHeader, &peData, &optHeaderEntries, &optHeaders);
fclose(basefile);
fclose(xex);
return -1;
}
// Final pass (sets & writes header hash)
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 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

@ -20,69 +20,165 @@
struct sectionInfo struct sectionInfo
{ {
uint32_t virtualSize; uint32_t virtualSize;
uint32_t rva; uint32_t rva;
uint32_t rawSize; uint32_t rawSize;
uint32_t offset; 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) // Maps the PE file into the basefile (RVAs become offsets)
int mapPEToBasefile(FILE *pe, FILE *basefile, struct peData *peData) 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)); 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
for(uint16_t i = 0; i < peData->numberOfSections; i++) 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); sectionInfo[i].virtualSize = get32BitFromPE(pe);
sectionInfo[i].rva = get32BitFromPE(pe);
sectionInfo[i].rawSize = get32BitFromPE(pe);
sectionInfo[i].offset = get32BitFromPE(pe);
fseek(pe, 0x18, SEEK_CUR); // Seek to next entry at virtualSize
}
// 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;}
fread(buffer, sizeof(uint8_t), peData->headerSize + peData->sectionTableSize, pe); if(errno != SUCCESS)
fwrite(buffer, sizeof(uint8_t), peData->headerSize + peData->sectionTableSize, basefile); { return errno; }
// Now map the sections and write them sectionInfo[i].rva = get32BitFromPE(pe);
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;}
fseek(pe, sectionInfo[i].offset, SEEK_SET);
fread(buffer, sizeof(uint8_t), sectionInfo[i].rawSize, pe);
fseek(basefile, sectionInfo[i].rva, SEEK_SET); if(errno != SUCCESS)
fwrite(buffer, sizeof(uint8_t), sectionInfo[i].rawSize, basefile); { return errno; }
sectionInfo[i].rawSize = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
sectionInfo[i].offset = get32BitFromPE(pe);
if(errno != SUCCESS)
{ return errno; }
// Seek to the next entry at virtualSize
if(fseek(pe, 0x18, SEEK_CUR) != 0)
{ return ERR_FILE_READ; }
} }
// Pad the rest of the final page with zeroes, we can achieve this by seeking if(fseek(pe, 0, SEEK_SET) != 0)
// to the end and placing a single zero there (unless the data runs all the way up to the end!) { return ERR_FILE_READ; }
uint32_t currentOffset = ftell(basefile);
uint32_t nextAligned = getNextAligned(currentOffset, peData->pageSize) - 0x1;
if(nextAligned != currentOffset) // 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, 1 * sizeof(uint8_t)); buffer = realloc(buffer, sectionInfo[i].rawSize);
if(buffer == NULL) {return ERR_OUT_OF_MEM;}
buffer[0] = 0; if(!buffer)
fseek(basefile, nextAligned, SEEK_SET); { return ERR_OUT_OF_MEM; }
fwrite(buffer, sizeof(uint8_t), 1, 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; }
} }
// Make sure to update the PE (basefile) size // Pad the rest of the final page with zeroes, we can achieve this by seeking
peData->size = ftell(basefile); // 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);
// We're done with these, free them uint32_t nextAligned = getNextAligned(currentOffset, peData->pageSize) - 1;
free(buffer);
free(sectionInfo);
return SUCCESS; if(nextAligned != currentOffset)
{
buffer = realloc(buffer, 1);
if(!buffer)
{ return ERR_OUT_OF_MEM; }
buffer[0] = 0;
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 now, free them
nullAndFree((void **)&buffer);
nullAndFree((void **)&sectionInfo);
// 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

@ -21,85 +21,89 @@
// Internal struct, defined here so it cannot be accessed outwith this file // Internal struct, defined here so it cannot be accessed outwith this file
struct importLibIdcs struct importLibIdcs
{ {
uint32_t header; uint32_t header;
uint32_t entry; uint32_t entry;
}; };
int setOptHeaderOffsets(struct offsets *offsets, struct optHeaderEntries *optHeaderEntries, struct optHeaders *optHeaders, uint32_t *currentOffset, struct importLibIdcs *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. // Calloc because 0 values will be used to determine if a header is not present.
if(offsets->optHeaders == NULL) {return ERR_OUT_OF_MEM;} offsets->optHeaders = calloc(optHeaderEntries->count, sizeof(uint32_t));
uint32_t sepHeader = 0; // Separate header iterator, i.e. one with it's data outwith the entries
if(offsets->optHeaders == NULL)
for(uint32_t i = 0; i < optHeaderEntries->count; i++) { 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++)
{ {
*currentOffset = getNextAligned(*currentOffset, 0x8); *currentOffset = getNextAligned(*currentOffset, 0x8);
switch(optHeaderEntries->optHeaderEntry[i].id)
{
case XEX_OPT_ID_BASEFILE_FORMAT:
optHeaderEntries->optHeaderEntry[i].dataOrOffset = *currentOffset;
offsets->optHeaders[sepHeader] = *currentOffset;
*currentOffset += sizeof(struct basefileFormat);
sepHeader++;
break;
case XEX_OPT_ID_IMPORT_LIBS: switch(optHeaderEntries->optHeaderEntry[i].id)
importLibIdcs->header = sepHeader; {
importLibIdcs->entry = i; case XEX_OPT_ID_BASEFILE_FORMAT:
sepHeader++; optHeaderEntries->optHeaderEntry[i].dataOrOffset = *currentOffset;
break; offsets->optHeaders[sepHeader] = *currentOffset;
*currentOffset += sizeof(struct basefileFormat);
sepHeader++;
break;
case XEX_OPT_ID_TLS_INFO: case XEX_OPT_ID_IMPORT_LIBS:
optHeaderEntries->optHeaderEntry[i].dataOrOffset = *currentOffset; importLibIdcs->header = sepHeader;
offsets->optHeaders[sepHeader] = *currentOffset; importLibIdcs->entry = i;
*currentOffset += sizeof(struct tlsInfo); sepHeader++;
sepHeader++; break;
break;
} case XEX_OPT_ID_TLS_INFO:
optHeaderEntries->optHeaderEntry[i].dataOrOffset = *currentOffset;
offsets->optHeaders[sepHeader] = *currentOffset;
*currentOffset += sizeof(struct tlsInfo);
sepHeader++;
break;
}
} }
return SUCCESS; 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) int placeStructs(struct offsets *offsets, struct xexHeader *xexHeader, struct optHeaderEntries *optHeaderEntries, struct secInfoHeader *secInfoHeader, struct optHeaders *optHeaders)
{ {
// XEX Header // XEX Header
uint32_t currentOffset = 0x0; uint32_t currentOffset = 0x0;
offsets->xexHeader = currentOffset; offsets->xexHeader = currentOffset;
currentOffset += sizeof(struct xexHeader); currentOffset += sizeof(struct xexHeader);
// Optional header entries (no alignment, they immediately follow XEX header) // Optional header entries (no alignment, they immediately follow XEX header)
offsets->optHeaderEntries = currentOffset; offsets->optHeaderEntries = currentOffset;
currentOffset += optHeaderEntries->count * sizeof(struct optHeaderEntry); currentOffset += optHeaderEntries->count *sizeof(struct optHeaderEntry);
// Security header
currentOffset = getNextAligned(currentOffset, 0x8); // 8-byte alignment for these headers etc
offsets->secInfoHeader = currentOffset;
xexHeader->secInfoOffset = currentOffset;
currentOffset += (sizeof(struct secInfoHeader) - sizeof(void*)) + (secInfoHeader->pageDescCount * sizeof(struct pageDescriptor));
// Optional headers (minus imports) // Security header
struct importLibIdcs importLibIdcs; currentOffset = getNextAligned(currentOffset, 0x8); // 8-byte alignment for these headers, at least 8 bytes beyond end of optional header entries
uint32_t importLibsIdx; // Entry in opt header entries of import libs offsets->secInfoHeader = currentOffset;
int ret = setOptHeaderOffsets(offsets, optHeaderEntries, optHeaders, &currentOffset, &importLibIdcs); xexHeader->secInfoOffset = currentOffset;
currentOffset += (sizeof(struct secInfoHeader) - sizeof(void *)) + (secInfoHeader->pageDescCount *sizeof(struct pageDescriptor));
if(ret != SUCCESS) // Optional headers (minus imports)
struct importLibIdcs importLibIdcs;
int ret = setOptHeaderOffsets(offsets, optHeaderEntries, optHeaders, &currentOffset, &importLibIdcs);
if(ret != SUCCESS)
{ return ret; }
currentOffset += optHeaders->importLibraries.size; // Reserving bytes for imports
// PE basefile
currentOffset = getNextAligned(currentOffset, 0x1000); // 4KiB alignment for basefile
offsets->basefile = currentOffset;
xexHeader->peOffset = currentOffset;
// Imports, the end of this header is aligned to the start of the basefile, so they are a special case
if(optHeaders->importLibraries.tableCount > 0)
{ {
return ret; offsets->optHeaders[importLibIdcs.header] = offsets->basefile - optHeaders->importLibraries.size;
optHeaderEntries->optHeaderEntry[importLibIdcs.entry].dataOrOffset = offsets->optHeaders[importLibIdcs.header];
} }
//currentOffset += optHeaders->importLibraries.size; // Reserving bytes for imports
// PE basefile
currentOffset = getNextAligned(currentOffset, 0x1000); // 4KiB alignment for basefile
offsets->basefile = currentOffset;
xexHeader->peOffset = currentOffset;
// Imports, the end of this header is aligned to the start of the basefile, so they are a special case return SUCCESS;
//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 // This file is part of SynthXEX, one component of the
// FreeChainXenon development toolchain // 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 // 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 // it under the terms of the GNU Affero General Public License as published by
@ -20,55 +20,335 @@
void setBasefileFormat(struct basefileFormat *basefileFormat, struct secInfoHeader *secInfoHeader) void setBasefileFormat(struct basefileFormat *basefileFormat, struct secInfoHeader *secInfoHeader)
{ {
basefileFormat->size = (1 * 8) + 8; // (Block count * size of raw data descriptor) + size of data descriptor basefileFormat->size = (1 * 8) + 8; // (Block count * size of raw data descriptor) + size of data descriptor
basefileFormat->encType = 0x0; // No encryption basefileFormat->encType = 0x0; // No encryption
basefileFormat->compType = 0x1; // No compression basefileFormat->compType = 0x1; // No compression
basefileFormat->dataSize = secInfoHeader->peSize; basefileFormat->dataSize = secInfoHeader->peSize;
basefileFormat->zeroSize = 0x0; // We aren't going to be removing any zeroes. TODO: implement this, it can make files much smaller basefileFormat->zeroSize = 0x0; // We aren't going to be removing any zeroes. TODO: implement this, it can make files much smaller
} }
// STUB. TLS info not supported. // STUB. TLS info not supported.
void setTLSInfo(struct tlsInfo *tlsInfo) void setTLSInfo(struct tlsInfo *tlsInfo)
{ {
tlsInfo->slotCount = 0x40; tlsInfo->slotCount = 0x40;
tlsInfo->rawDataAddr = 0x0; tlsInfo->rawDataAddr = 0x0;
tlsInfo->dataSize = 0x0; tlsInfo->dataSize = 0x0;
tlsInfo->rawDataSize = 0x0; 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 // STUB. TODO: Dynamically select, and/or allow user to select, flags
void setSysFlags(uint32_t *flags) void setSysFlags(uint32_t *flags)
{ {
*flags = XEX_SYS_GAMEPAD_DISCONNECT | if(flags == NULL)
XEX_SYS_INSECURE_SOCKETS | { return; }
XEX_SYS_XAM_HOOKS |
XEX_SYS_BACKGROUND_DL | *flags = XEX_SYS_GAMEPAD_DISCONNECT |
XEX_SYS_ALLOW_CONTROL_SWAP; XEX_SYS_INSECURE_SOCKETS |
XEX_SYS_XAM_HOOKS |
XEX_SYS_BACKGROUND_DL |
XEX_SYS_ALLOW_CONTROL_SWAP;
} }
int setOptHeaders(struct secInfoHeader *secInfoHeader, struct peData *peData, struct optHeaderEntries *optHeaderEntries, struct optHeaders *optHeaders) 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)
setBasefileFormat(&(optHeaders->basefileFormat), secInfoHeader);
optHeaderEntries->optHeaderEntry[0].id = XEX_OPT_ID_BASEFILE_FORMAT;
// Second optional header (entrypoint) optHeaderEntries->count = 4;
optHeaderEntries->optHeaderEntry[1].id = XEX_OPT_ID_ENTRYPOINT;
optHeaderEntries->optHeaderEntry[1].dataOrOffset = secInfoHeader->baseAddr + peData->entryPoint;
// Third optional header (import libs) if(importsPresent)
//optHeaderEntries->optHeaderEntry[2].id = XEX_OPT_ID_IMPORT_LIBS; { optHeaderEntries->count++; }
// Fourth optional header (tls info) optHeaderEntries->optHeaderEntry = calloc(optHeaderEntries->count, sizeof(struct optHeaderEntry));
optHeaderEntries->optHeaderEntry[2].id = XEX_OPT_ID_TLS_INFO;
setTLSInfo(&(optHeaders->tlsInfo));
// Fifth optional header (system flags) if(optHeaderEntries->optHeaderEntry == NULL)
optHeaderEntries->optHeaderEntry[3].id = XEX_OPT_ID_SYS_FLAGS; { return ERR_OUT_OF_MEM; }
setSysFlags(&(optHeaderEntries->optHeaderEntry[3].dataOrOffset));
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[currentHeader].id = XEX_OPT_ID_BASEFILE_FORMAT;
currentHeader++;
// Entrypoint (0x10100)
optHeaderEntries->optHeaderEntry[currentHeader].id = XEX_OPT_ID_ENTRYPOINT;
optHeaderEntries->optHeaderEntry[currentHeader].dataOrOffset = secInfoHeader->baseAddr + peData->entryPoint;
currentHeader++;
// Import libraries (0x103FF)
if(importsPresent)
{
optHeaderEntries->optHeaderEntry[currentHeader].id = XEX_OPT_ID_IMPORT_LIBS;
int ret = setImportLibsInfo(&(optHeaders->importLibraries), &(peData->peImportInfo), secInfoHeader);
if(ret != SUCCESS)
{ return ret; }
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 #pragma once
#include "../common/common.h" #include "../common/common.h"
#include "../common/crypto.h"
#include "../common/datastorage.h" #include "../common/datastorage.h"
int setOptHeaders(struct secInfoHeader *secInfoHeader, struct peData *peData, struct optHeaderEntries *optHeaderEntries, struct optHeaders *optHeaders); int setOptHeaders(struct secInfoHeader *secInfoHeader, struct peData *peData, struct optHeaderEntries *optHeaderEntries, struct optHeaders *optHeaders);

View file

@ -20,61 +20,70 @@
uint8_t getRwx(struct secInfoHeader *secInfoHeader, struct peData *peData, uint32_t page) uint8_t getRwx(struct secInfoHeader *secInfoHeader, struct peData *peData, uint32_t page)
{ {
uint32_t pageSize = secInfoHeader->peSize / secInfoHeader->pageDescCount; uint32_t pageSize = secInfoHeader->peSize / secInfoHeader->pageDescCount;
uint32_t currentOffset = page * pageSize; 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;
}
}
return XEX_SECTION_RODATA | 0b10000; // We're in the PE header, so RODATA 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
} }
int setPageDescriptors(FILE *pe, struct peData *peData, struct secInfoHeader *secInfoHeader) int setPageDescriptors(FILE *pe, struct peData *peData, struct secInfoHeader *secInfoHeader)
{ {
uint32_t pageSize = secInfoHeader->peSize / secInfoHeader->pageDescCount; 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;}
// 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);
// 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);
// For little endian systems, swap into big endian for hashing, then back (to keep struct endianness consistent) 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)
descriptors[i].sizeAndInfo = getRwx(secInfoHeader, peData, i);
// Init sha1 hash
struct sha1_ctx shaContext;
sha1_init(&shaContext);
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 #ifdef LITTLE_ENDIAN_SYSTEM
secInfoHeader->descriptors[i].sizeAndInfo = __builtin_bswap32(secInfoHeader->descriptors[i].sizeAndInfo); descriptors[i].sizeAndInfo = __builtin_bswap32(descriptors[i].sizeAndInfo);
#endif #endif
sha1_update(&shaContext, pageSize, page); 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 #ifdef LITTLE_ENDIAN_SYSTEM
secInfoHeader->descriptors[i].sizeAndInfo = __builtin_bswap32(secInfoHeader->descriptors[i].sizeAndInfo); descriptors[i].sizeAndInfo = __builtin_bswap32(descriptors[i].sizeAndInfo);
#endif #endif
if(i != 0) if(i != 0)
{ { sha1_digest(&shaContext, 0x14, descriptors[i - 1].sha1); }
sha1_digest(&shaContext, 0x14, secInfoHeader->descriptors[i - 1].sha1); else
} { sha1_digest(&shaContext, 0x14, secInfoHeader->imageSha1); }
else
{ nullAndFree((void **)&page);
sha1_digest(&shaContext, 0x14, secInfoHeader->imageSha1);
}
} }
free(peData->sections.sectionPerms); // Alloc'd in getdata return SUCCESS;
} }

View file

@ -18,49 +18,54 @@
#include "populateheaders.h" #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. // Writing data into XEX header.
strcpy(xexHeader->magic, "XEX2"); // Magic strncpy(xexHeader->magic, "XEX2", sizeof(char) * 4); // Magic
// Module flags (type of executable) // 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 if(peData->characteristics & PE_CHAR_FLAG_DLL)
} {
else xexHeader->moduleFlags |= XEX_MOD_FLAG_DLL; // The executable is a DLL
{ }
xexHeader->moduleFlags |= XEX_MOD_FLAG_TITLE; // The executable is a regular title else
{
xexHeader->moduleFlags |= XEX_MOD_FLAG_TITLE; // The executable is a regular title
}
if(peData->peExportInfo.count > 0)
{
xexHeader->moduleFlags |= XEX_MOD_FLAG_EXPORTS; // The executable exports functions
}
} }
if(peData->peExportInfo.count > 0) xexHeader->optHeaderCount = optHeaderEntries->count;
{
xexHeader->moduleFlags |= XEX_MOD_FLAG_EXPORTS; // The executable exports functions return SUCCESS;
}
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) int setSecInfoHeader(struct secInfoHeader *secInfoHeader, struct peData *peData)
{ {
// Writing data into security info header (much of this is derived from info in PE) // Writing data into security info header (much of this is derived from info in PE)
secInfoHeader->peSize = peData->size; secInfoHeader->peSize = peData->size;
// Setting signature. Clearing first to ensure no memory contents are written to file, then adding identifier. // Setting signature (just a SynthXEX version identifier)
memset(secInfoHeader->signature, 0, sizeof(secInfoHeader->signature)); strcpy(secInfoHeader->signature, SYNTHXEX_VERSION_STRING);
strcpy(secInfoHeader->signature, VERSION_STRING);
secInfoHeader->imageInfoSize = 0x174; // Image info size is always 0x174 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->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; secInfoHeader->baseAddr = peData->baseAddr;
memset(secInfoHeader->mediaID, 0, sizeof(secInfoHeader->mediaID)); // Null media ID //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 //memset(secInfoHeader->aesKey, 0, sizeof(secInfoHeader->aesKey)); // No encryption, null AES key
//secInfoHeader->exportTableAddr = TEMPEXPORTADDR; //secInfoHeader->exportTableAddr = TEMPEXPORTADDR;
secInfoHeader->exportTableAddr = 0; secInfoHeader->exportTableAddr = 0;
secInfoHeader->gameRegion = XEX_REG_FLAG_REGION_FREE; secInfoHeader->gameRegion = XEX_REG_FLAG_REGION_FREE;
secInfoHeader->mediaTypes = 0xFFFFFFFF; // All flags set, can load from any type. 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->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) 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/common.h"
#include "../common/datastorage.h" #include "../common/datastorage.h"
void setXEXHeader(struct xexHeader *xexHeader, struct peData *peData); int setXEXHeader(struct xexHeader *xexHeader, struct optHeaderEntries *optHeaderEntries, struct peData *peData);
void setSecInfoHeader(struct secInfoHeader *secInfoHeader, struct peData *peData); int setSecInfoHeader(struct secInfoHeader *secInfoHeader, struct peData *peData);

View file

@ -18,71 +18,94 @@
#include "headerhash.h" #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 // 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. // 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
uint32_t basefileOffset = get32BitFromXEX(xex); if(fseek(xex, 0x8, SEEK_SET) != 0)
{ return ERR_FILE_READ; }
fseek(xex, 0x10, SEEK_SET); // Secinfo offset
uint32_t secInfoOffset = get32BitFromXEX(xex);
uint32_t endOfImageInfo = secInfoOffset + 0x8 + 0x174; // 0x8 == image info offset in security info, 0x174 == length of that uint32_t basefileOffset = get32BitFromXEX(xex);
uint32_t remainingSize = basefileOffset - endOfImageInfo; // How much data is between end of image info and basefile (we hash that too)
// Init sha1 hash if(errno != SUCCESS)
struct sha1_ctx shaContext; { return errno; }
sha1_init(&shaContext);
// Hash first part (remainder of headers is done first, then the start) // Get secinfo offset
uint8_t remainderOfHeaders[remainingSize]; if(fseek(xex, 0x10, SEEK_SET) != 0)
fseek(xex, endOfImageInfo, SEEK_SET); { return ERR_FILE_READ; }
fread(remainderOfHeaders, sizeof(uint8_t), remainingSize, xex);
sha1_update(&shaContext, remainingSize, remainderOfHeaders);
uint8_t headersStart[secInfoOffset + 0x8]; // Hash from start up to image info (0x8 into security header)
fseek(xex, 0, SEEK_SET);
fread(headersStart, sizeof(uint8_t), secInfoOffset + 0x8, xex);
sha1_update(&shaContext, secInfoOffset + 0x8, headersStart);
// Get final hash
uint8_t headerHash[20];
sha1_digest(&shaContext, 20, headerHash);
// Finally, write it out uint32_t secInfoOffset = get32BitFromXEX(xex);
fseek(xex, secInfoOffset + 0x164, SEEK_SET); // 0x164 == offset in secinfo of header hash
fwrite(headerHash, sizeof(uint8_t), 20, 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 = 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);
// Hash from start up to image info (0x8 into security header)
uint32_t headersLen = secInfoOffset + 0x8;
uint8_t *headersStart = malloc(headersLen);
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
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 // This file is part of SynthXEX, one component of the
// FreeChainXenon development toolchain // 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 // 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 // it under the terms of the GNU Affero General Public License as published by
@ -22,5 +22,4 @@
#include "../common/crypto.h" #include "../common/crypto.h"
#include "../common/datastorage.h" #include "../common/datastorage.h"
void setImportsSha1(FILE *xex); int setHeaderSha1(FILE *xex);
void setHeaderSha1(FILE *xex);

View file

@ -1,7 +1,7 @@
// This file is part of SynthXEX, one component of the // This file is part of SynthXEX, one component of the
// FreeChainXenon development toolchain // 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 // 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 // it under the terms of the GNU Affero General Public License as published by
@ -21,134 +21,186 @@
// TEMPORARY WRITE TESTING // TEMPORARY WRITE TESTING
int writeXEX(struct xexHeader *xexHeader, struct optHeaderEntries *optHeaderEntries, struct secInfoHeader *secInfoHeader, struct optHeaders *optHeaders, struct offsets *offsets, FILE *pe, FILE *xex) int writeXEX(struct xexHeader *xexHeader, struct optHeaderEntries *optHeaderEntries, struct secInfoHeader *secInfoHeader, struct optHeaders *optHeaders, struct offsets *offsets, FILE *pe, FILE *xex)
{ {
// XEX Header // XEX Header
#ifdef LITTLE_ENDIAN_SYSTEM #ifdef LITTLE_ENDIAN_SYSTEM
// Endian-swap XEX header before writing // Endian-swap XEX header before writing
xexHeader->moduleFlags = __builtin_bswap32(xexHeader->moduleFlags); xexHeader->moduleFlags = __builtin_bswap32(xexHeader->moduleFlags);
xexHeader->peOffset = __builtin_bswap32(xexHeader->peOffset); xexHeader->peOffset = __builtin_bswap32(xexHeader->peOffset);
xexHeader->secInfoOffset = __builtin_bswap32(xexHeader->secInfoOffset); xexHeader->secInfoOffset = __builtin_bswap32(xexHeader->secInfoOffset);
xexHeader->optHeaderCount = __builtin_bswap32(xexHeader->optHeaderCount); xexHeader->optHeaderCount = __builtin_bswap32(xexHeader->optHeaderCount);
#endif
fseek(xex, offsets->xexHeader, SEEK_SET);
fwrite(xexHeader, sizeof(uint8_t), sizeof(struct xexHeader), xex);
// 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 #endif
fseek(xex, offsets->optHeaderEntries, SEEK_SET); fseek(xex, offsets->xexHeader, SEEK_SET);
fwrite(xexHeader, sizeof(uint8_t), sizeof(struct xexHeader), xex);
for(int i = 0; i < optHeaderEntries->count; i++) // Optional header entries
{
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.
// Page descriptors
fseek(xex, offsets->secInfoHeader + sizeof(struct secInfoHeader) - sizeof(void*), SEEK_SET);
for(int i = 0; i < secInfoHeader->pageDescCount; i++)
{
#ifdef LITTLE_ENDIAN_SYSTEM #ifdef LITTLE_ENDIAN_SYSTEM
secInfoHeader->descriptors[i].sizeAndInfo = __builtin_bswap32(secInfoHeader->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);
}
free(secInfoHeader->descriptors); // calloc'd elsewhere, freeing now // Endian swap opt header entries
for(uint32_t i = 0; i < optHeaderEntries->count; i++)
// 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;}
for(uint32_t i = 0; i < secInfoHeader->peSize; i += readBufSize)
{ {
fread(buffer, sizeof(uint8_t), readBufSize, pe); optHeaderEntries->optHeaderEntry[i].id = __builtin_bswap32(optHeaderEntries->optHeaderEntry[i].id);
fwrite(buffer, sizeof(uint8_t), readBufSize, xex); optHeaderEntries->optHeaderEntry[i].dataOrOffset = __builtin_bswap32(optHeaderEntries->optHeaderEntry[i].dataOrOffset);
} }
free(buffer);
// Security Info
#ifdef LITTLE_ENDIAN_SYSTEM
// Endian-swap secinfo header
secInfoHeader->headerSize = __builtin_bswap32(secInfoHeader->headerSize);
secInfoHeader->peSize = __builtin_bswap32(secInfoHeader->peSize);
secInfoHeader->imageInfoSize = __builtin_bswap32(secInfoHeader->imageInfoSize);
secInfoHeader->imageFlags = __builtin_bswap32(secInfoHeader->imageFlags);
secInfoHeader->baseAddr = __builtin_bswap32(secInfoHeader->baseAddr);
secInfoHeader->importTableCount = __builtin_bswap32(secInfoHeader->importTableCount);
secInfoHeader->exportTableAddr = __builtin_bswap32(secInfoHeader->exportTableAddr);
secInfoHeader->gameRegion = __builtin_bswap32(secInfoHeader->gameRegion);
secInfoHeader->mediaTypes = __builtin_bswap32(secInfoHeader->mediaTypes);
secInfoHeader->pageDescCount = __builtin_bswap32(secInfoHeader->pageDescCount);
#endif #endif
fseek(xex, offsets->secInfoHeader, SEEK_SET); fseek(xex, offsets->optHeaderEntries, SEEK_SET);
fwrite(secInfoHeader, sizeof(uint8_t), sizeof(struct secInfoHeader) - sizeof(void*), xex); // sizeof(void*) == size of page descriptor pointer at end
// Optional headers
uint32_t currentHeader = 0;
if(optHeaders->basefileFormat.size != 0) // If not 0, it has data. Write it.
{
fseek(xex, offsets->optHeaders[currentHeader], SEEK_SET);
#ifdef LITTLE_ENDIAN_SYSTEM for(int i = 0; i < optHeaderEntries->count; i++)
optHeaders->basefileFormat.size = __builtin_bswap32(optHeaders->basefileFormat.size); { fwrite(&(optHeaderEntries->optHeaderEntry[i]), sizeof(uint8_t), sizeof(struct optHeaderEntry), xex); }
optHeaders->basefileFormat.encType = __builtin_bswap16(optHeaders->basefileFormat.encType);
optHeaders->basefileFormat.compType = __builtin_bswap16(optHeaders->basefileFormat.compType);
optHeaders->basefileFormat.dataSize = __builtin_bswap32(optHeaders->basefileFormat.dataSize);
optHeaders->basefileFormat.zeroSize = __builtin_bswap32(optHeaders->basefileFormat.zeroSize);
#endif
fwrite(&(optHeaders->basefileFormat), sizeof(uint8_t), sizeof(struct basefileFormat), xex);
currentHeader++;
}
if(optHeaders->importLibraries.size != 0) // 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++)
{ {
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
#ifdef LITTLE_ENDIAN_SYSTEM #ifdef LITTLE_ENDIAN_SYSTEM
optHeaders->importLibraries.size = __builtin_bswap32(optHeaders->importLibraries.size); descriptors[i].sizeAndInfo = __builtin_bswap32(descriptors[i].sizeAndInfo);
#endif #endif
fwrite(&(optHeaders->importLibraries.size), sizeof(uint8_t), 0x4, xex); // Writing out current descriptor...
fwrite(optHeaders->importLibraries.data, sizeof(uint8_t), importLibsSize - 0x4, xex); fwrite(&(descriptors[i].sizeAndInfo), sizeof(uint32_t), 0x1, xex);
currentHeader++; fwrite(descriptors[i].sha1, sizeof(uint8_t), 0x14, xex);
} }
if(optHeaders->tlsInfo.slotCount != 0) // Basefile
{ fseek(pe, 0x0, SEEK_SET);
fseek(xex, offsets->optHeaders[currentHeader], 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; }
for(uint32_t i = 0; i < secInfoHeader->peSize; i += readBufSize)
{
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; }
}
nullAndFree((void **)&buffer);
// Security Info
#ifdef LITTLE_ENDIAN_SYSTEM #ifdef LITTLE_ENDIAN_SYSTEM
optHeaders->tlsInfo.slotCount = __builtin_bswap32(optHeaders->tlsInfo.slotCount); // Endian-swap secinfo header
optHeaders->tlsInfo.rawDataAddr = __builtin_bswap32(optHeaders->tlsInfo.rawDataAddr); secInfoHeader->headerSize = __builtin_bswap32(secInfoHeader->headerSize);
optHeaders->tlsInfo.dataSize = __builtin_bswap32(optHeaders->tlsInfo.dataSize); secInfoHeader->peSize = __builtin_bswap32(secInfoHeader->peSize);
optHeaders->tlsInfo.rawDataSize = __builtin_bswap32(optHeaders->tlsInfo.rawDataSize); secInfoHeader->imageInfoSize = __builtin_bswap32(secInfoHeader->imageInfoSize);
secInfoHeader->imageFlags = __builtin_bswap32(secInfoHeader->imageFlags);
secInfoHeader->baseAddr = __builtin_bswap32(secInfoHeader->baseAddr);
secInfoHeader->importTableCount = __builtin_bswap32(secInfoHeader->importTableCount);
secInfoHeader->exportTableAddr = __builtin_bswap32(secInfoHeader->exportTableAddr);
secInfoHeader->gameRegion = __builtin_bswap32(secInfoHeader->gameRegion);
secInfoHeader->mediaTypes = __builtin_bswap32(secInfoHeader->mediaTypes);
secInfoHeader->pageDescCount = __builtin_bswap32(secInfoHeader->pageDescCount);
#endif #endif
fwrite(&(optHeaders->tlsInfo), sizeof(uint8_t), sizeof(struct tlsInfo), xex); fseek(xex, offsets->secInfoHeader, SEEK_SET);
fwrite(secInfoHeader, sizeof(uint8_t), sizeof(struct secInfoHeader) - sizeof(void *), xex); // sizeof(void*) == size of page descriptor pointer at end
// Optional headers
uint32_t currentHeader = 0;
if(optHeaders->basefileFormat.size != 0) // If not 0, it has data. Write it.
{
fseek(xex, offsets->optHeaders[currentHeader], SEEK_SET);
#ifdef LITTLE_ENDIAN_SYSTEM
optHeaders->basefileFormat.size = __builtin_bswap32(optHeaders->basefileFormat.size);
optHeaders->basefileFormat.encType = __builtin_bswap16(optHeaders->basefileFormat.encType);
optHeaders->basefileFormat.compType = __builtin_bswap16(optHeaders->basefileFormat.compType);
optHeaders->basefileFormat.dataSize = __builtin_bswap32(optHeaders->basefileFormat.dataSize);
optHeaders->basefileFormat.zeroSize = __builtin_bswap32(optHeaders->basefileFormat.zeroSize);
#endif
fwrite(&(optHeaders->basefileFormat), sizeof(uint8_t), sizeof(struct basefileFormat), xex);
currentHeader++;
} }
free(offsets->optHeaders); // Alloc'd in placer. if(optHeaders->importLibraries.size != 0)
return SUCCESS; {
fseek(xex, offsets->optHeaders[currentHeader], SEEK_SET);
// 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), 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++;
}
if(optHeaders->tlsInfo.slotCount != 0)
{
fseek(xex, offsets->optHeaders[currentHeader], SEEK_SET);
#ifdef LITTLE_ENDIAN_SYSTEM
optHeaders->tlsInfo.slotCount = __builtin_bswap32(optHeaders->tlsInfo.slotCount);
optHeaders->tlsInfo.rawDataAddr = __builtin_bswap32(optHeaders->tlsInfo.rawDataAddr);
optHeaders->tlsInfo.dataSize = __builtin_bswap32(optHeaders->tlsInfo.dataSize);
optHeaders->tlsInfo.rawDataSize = __builtin_bswap32(optHeaders->tlsInfo.rawDataSize);
#endif
fwrite(&(optHeaders->tlsInfo), sizeof(uint8_t), sizeof(struct tlsInfo), xex);
}
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