├── .clang-format
├── .devcontainer
└── devcontainer.json
├── .github
└── workflows
│ ├── ci.yml
│ ├── compare.yml
│ ├── docker.yml
│ ├── export-ghidra.yml
│ └── update-mapping.yml
├── .gitignore
├── .gitmodules
├── .pre-commit-config.yaml
├── LICENSE
├── README.md
├── config
├── ghidra-user-maps.toml
├── ghidra_ns_to_obj.csv
├── globals.csv
├── implemented.csv
├── mapping.csv
├── order.txt
└── stubbed.csv
├── objdiff.json
├── resources
├── placeholder.ico
├── placeholder.png
├── progress.svg
├── progress_dark.svg
├── progress_template.svg
└── th06.rc
├── scripts
├── Dockerfile
├── __init__.py
├── build.py
├── coff.py
├── configure.py
├── create_devenv.py
├── create_th06_prefix
├── diff_all_functions.py
├── export_ghidra_database.py
├── export_ghidra_objs.py
├── extract_icon.py
├── gendef.py
├── generate_decompme_toolchain.py
├── generate_detours.py
├── generate_function_decompme.py
├── generate_function_diff.py
├── generate_globals.py
├── generate_objdiff_objs.py
├── generate_stubs.py
├── ghidra
│ ├── ExportDecomp.java
│ ├── ExportDelinker.java
│ ├── ExportFileVersions.java
│ ├── ExportToXML.java
│ ├── GenerateMapping.java
│ ├── ImportFromCsv.java
│ ├── ImportFromXml.java
│ └── debug.py
├── ghidra_helpers.py
├── icon_extractor.py
├── ninja_syntax.py
├── pefile.py
├── pragma_var_order.cpp
├── test.py
├── th06run.bat
├── th06vars.bat
├── update_decompile_stats.py
├── update_mapping.py
└── winhelpers.py
├── src
├── AnmIdx.hpp
├── AnmManager.cpp
├── AnmManager.hpp
├── AnmVm.hpp
├── AsciiManager.cpp
├── AsciiManager.hpp
├── BombData.cpp
├── BombData.hpp
├── BulletData.cpp
├── BulletData.hpp
├── BulletManager.cpp
├── BulletManager.hpp
├── CMyFont.cpp
├── CMyFont.hpp
├── Chain.cpp
├── Chain.hpp
├── ChainPriorities.hpp
├── Controller.cpp
├── Controller.hpp
├── EclManager.cpp
├── EclManager.hpp
├── Effect.hpp
├── EffectManager.cpp
├── EffectManager.hpp
├── Ending.cpp
├── Ending.hpp
├── Enemy.hpp
├── EnemyEclInstr.cpp
├── EnemyEclInstr.hpp
├── EnemyManager.cpp
├── EnemyManager.hpp
├── FileSystem.cpp
├── FileSystem.hpp
├── GameErrorContext.cpp
├── GameErrorContext.hpp
├── GameManager.cpp
├── GameManager.hpp
├── GameWindow.cpp
├── GameWindow.hpp
├── Gui.cpp
├── Gui.hpp
├── ItemManager.cpp
├── ItemManager.hpp
├── MainMenu.cpp
├── MainMenu.hpp
├── MidiOutput.cpp
├── MidiOutput.hpp
├── MusicRoom.cpp
├── MusicRoom.hpp
├── Player.cpp
├── Player.hpp
├── ReplayData.hpp
├── ReplayManager.cpp
├── ReplayManager.hpp
├── ResultScreen.cpp
├── ResultScreen.hpp
├── Rng.cpp
├── Rng.hpp
├── ScreenEffect.cpp
├── ScreenEffect.hpp
├── SoundPlayer.cpp
├── SoundPlayer.hpp
├── Stage.cpp
├── Stage.hpp
├── StageMenu.hpp
├── Supervisor.cpp
├── Supervisor.hpp
├── TextHelper.cpp
├── TextHelper.hpp
├── ZunBool.hpp
├── ZunColor.hpp
├── ZunMath.hpp
├── ZunMemory.hpp
├── ZunResult.hpp
├── ZunTimer.cpp
├── ZunTimer.hpp
├── diffbuild.hpp
├── dllbuild.cpp
├── dxutil.hpp
├── i18n.tpl
├── inttypes.hpp
├── main.cpp
├── pbg3
│ ├── FileAbstraction.cpp
│ ├── FileAbstraction.hpp
│ ├── IPbg3Parser.cpp
│ ├── IPbg3Parser.hpp
│ ├── Pbg3Archive.cpp
│ ├── Pbg3Archive.hpp
│ ├── Pbg3Parser.cpp
│ └── Pbg3Parser.hpp
├── stubs.cpp
├── th06.hpp
├── th06.rc
├── utils.cpp
├── utils.hpp
├── zwave.cpp
└── zwave.hpp
└── tests
├── test_Pbg3Archive.cpp
└── tests.cpp
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: Microsoft
2 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "image": "ghcr.io/happyhavoc/th06:master",
3 | "features": {}
4 | }
5 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Build binary
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | jobs:
10 | devenv:
11 | runs-on: windows-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 | - name: Get toolchain from cache
15 | id: cache-toolchain
16 | uses: actions/cache/restore@v3
17 | with:
18 | # A directory to store and save the cache
19 | path: scripts/prefix
20 | # An explicit key for restoring and saving the cache
21 | key: prefix-${{hashFiles('scripts/create_devenv.py')}}
22 | # No need to download here.
23 | lookup-only: true
24 | - if: ${{ steps.cache-toolchain.outputs.cache-hit != 'true' }}
25 | name: Install prefix
26 | id: install_prefix
27 | run: python scripts/create_devenv.py scripts/dls scripts/prefix
28 | - if: always() && steps.cache-toolchain.outputs.cache-hit != 'true' && steps.install_prefix.outcome == 'success'
29 | name: Save toolchain to cache
30 | uses: actions/cache/save@v3
31 | with:
32 | # A directory to store and save the cache
33 | path: scripts/prefix
34 | # An explicit key for restoring and saving the cache
35 | key: ${{ steps.cache-toolchain.outputs.cache-primary-key }}
36 |
37 | build:
38 | needs: ['devenv']
39 | runs-on: windows-latest
40 | steps:
41 | - uses: actions/checkout@v3
42 | - uses: actions/setup-python@v5
43 | with:
44 | python-version: '3.4'
45 | - name: Get toolchain from cache
46 | id: cache-toolchain
47 | uses: actions/cache/restore@v3
48 | with:
49 | # A directory to store and save the cache
50 | path: scripts/prefix
51 | # An explicit key for restoring and saving the cache
52 | key: prefix-${{hashFiles('scripts/create_devenv.py')}}
53 | # This should never happen, as the devenv step plays before us.
54 | fail-on-cache-miss: true
55 | - name: Build binary
56 | run: python scripts/build.py -j0
57 | - uses: actions/upload-artifact@v4
58 | with:
59 | name: th06e
60 | path: |
61 | build/th06e.exe
62 | build/th06e.pdb
63 |
64 | build-diff:
65 | needs: ['devenv']
66 | runs-on: windows-latest
67 | steps:
68 | - uses: actions/checkout@v3
69 | - uses: actions/setup-python@v5
70 | with:
71 | python-version: '3.4'
72 | - name: Get toolchain from cache
73 | id: cache-toolchain
74 | uses: actions/cache/restore@v3
75 | with:
76 | # A directory to store and save the cache
77 | path: scripts/prefix
78 | # An explicit key for restoring and saving the cache
79 | key: prefix-${{hashFiles('scripts/create_devenv.py')}}
80 | # This should never happen, as the devenv step plays before us.
81 | fail-on-cache-miss: true
82 | - name: Build binary
83 | run: python scripts/build.py -j0 --build-type objdiffbuild
84 | - uses: actions/upload-artifact@v4
85 | with:
86 | name: th06e-diff
87 | path: |
88 | build/objdiff/reimpl
89 |
90 | build-dll:
91 | needs: ['devenv']
92 | runs-on: windows-latest
93 | steps:
94 | - uses: actions/checkout@v3
95 | with:
96 | submodules: true
97 | - uses: actions/setup-python@v5
98 | with:
99 | python-version: '3.4'
100 | - name: Get toolchain from cache
101 | id: cache-toolchain
102 | uses: actions/cache/restore@v3
103 | with:
104 | # A directory to store and save the cache
105 | path: scripts/prefix
106 | # An explicit key for restoring and saving the cache
107 | key: prefix-${{hashFiles('scripts/create_devenv.py')}}
108 | # This should never happen, as the devenv step plays before us.
109 | fail-on-cache-miss: true
110 | - name: Build binary
111 | run: python scripts/build.py -j0 --build-type dllbuild
112 | - uses: actions/upload-artifact@v4
113 | with:
114 | name: th06e-dll
115 | path: |
116 | build/th06e.dll
117 | build/th06e.pdb
118 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yml:
--------------------------------------------------------------------------------
1 | name: Publish devenv docker image
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | env:
7 | # Specify the image name as {owner}/{repo}.
8 | IMAGE_NAME: ${{ github.repository }}
9 |
10 | # Contents: read the contents of the repo (to build the image)
11 | # Packages: write the image to GHCR
12 | # ID-Token: create a token for cosign container signing
13 | permissions:
14 | contents: read
15 | packages: write
16 | id-token: write
17 |
18 | jobs:
19 | build:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - name: Checkout repository
23 | uses: actions/checkout@v2
24 | - name: Install cosign
25 | uses: sigstore/cosign-installer@v3.8.1
26 | with:
27 | cosign-release: 'v2.4.3'
28 | - name: Setup Docker buildx
29 | uses: docker/setup-buildx-action@v3.10.0
30 | with:
31 | version: latest
32 | - name: Log into registry ghcr
33 | uses: docker/login-action@v3.4.0
34 | with:
35 | registry: ghcr.io
36 | username: ${{ github.actor }}
37 | password: ${{ secrets.GITHUB_TOKEN }}
38 | - name: Docker meta
39 | id: meta
40 | uses: docker/metadata-action@v5
41 | with:
42 | images: ghcr.io/${{ env.IMAGE_NAME }}
43 | - name: Build and push Docker image
44 | id: build-and-push
45 | uses: docker/build-push-action@v6.15.0
46 | with:
47 | context: ./scripts
48 | push: true
49 | platforms: linux/amd64
50 | tags: ${{ steps.meta.outputs.tags }}
51 | labels: ${{ steps.meta.outputs.labels }}
52 | - name: Sign the published Docker image
53 | env:
54 | COSIGN_EXPERIMENTAL: "true"
55 | run: cosign sign -y ghcr.io/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}
56 |
--------------------------------------------------------------------------------
/.github/workflows/export-ghidra.yml:
--------------------------------------------------------------------------------
1 | name: Export Ghidra Database
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: '0 2 * * *'
7 |
8 | env:
9 | GHIDRA_VERSION: 11.1.2
10 | GHIDRA_DATE: 20240709
11 |
12 | jobs:
13 | export-ghidra:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | include:
19 | - branch: xml
20 | type: xml
21 | - branch: main
22 | type: decomp
23 |
24 | permissions:
25 | contents: write
26 |
27 | steps:
28 | - uses: actions/checkout@v4
29 | # Don't use actions/checkout@v4 because hurr durr it be broken: https://github.com/actions/checkout/issues/1477
30 | - name: Clone th06-re repo
31 | run: |
32 | echo "$GHIDRA_SSH_AUTH" > ssh_key
33 | chmod 0600 ssh_key
34 | GIT_SSH_COMMAND="ssh -i $PWD/ssh_key -o IdentitiesOnly=yes" git clone git@github.com:happyhavoc/th06-re th06-re --branch ${{ matrix.branch }}
35 | rm ssh_key
36 | env:
37 | GHIDRA_SSH_AUTH: ${{ secrets.GHIDRA_SSH_AUTH }}
38 | - name: Install python 3.11
39 | uses: actions/setup-python@v4
40 | with:
41 | python-version: '3.11'
42 | - name: Get ghidra
43 | run: |
44 | curl -L https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_${GHIDRA_VERSION}_build/ghidra_${GHIDRA_VERSION}_PUBLIC_${GHIDRA_DATE}.zip -o /tmp/ghidra.zip
45 | unzip -d /tmp /tmp/ghidra.zip
46 | echo /tmp/ghidra_*/support >> $GITHUB_PATH
47 | - name: Export ghidra
48 | run: |
49 | echo "$GHIDRA_SSH_AUTH" > ssh_key
50 | python scripts/export_ghidra_database.py --user-mappings config/ghidra-user-maps.toml --username github-action --ssh-key ssh_key --program 'th06_102h.exe' 'ghidra://roblab.la/Touhou/Touhou 06 - The Embodiment of Scarlet Devil/' th06-re ${{ matrix.type }}
51 | rm ssh_key
52 | env: # Or as an environment variable
53 | GHIDRA_SSH_AUTH: ${{ secrets.GHIDRA_SSH_AUTH }}
54 | - name: Push th06-re
55 | run: |
56 | echo "$GHIDRA_SSH_AUTH" > ssh_key
57 | chmod 0600 ssh_key
58 | GIT_SSH_COMMAND="ssh -i $PWD/ssh_key -o IdentitiesOnly=yes" git -C th06-re push origin HEAD
59 | rm ssh_key
60 | env:
61 | GHIDRA_SSH_AUTH: ${{ secrets.GHIDRA_SSH_AUTH }}
62 |
--------------------------------------------------------------------------------
/.github/workflows/update-mapping.yml:
--------------------------------------------------------------------------------
1 | name: Update mapping file
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: '0 2 * * *'
7 |
8 | env:
9 | GHIDRA_VERSION: 11.1.2
10 | GHIDRA_DATE: 20240709
11 |
12 | jobs:
13 | update-mapping:
14 | runs-on: ubuntu-latest
15 |
16 | permissions:
17 | contents: write
18 | pull-requests: write
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | - name: Install python 3.11
23 | uses: actions/setup-python@v4
24 | with:
25 | python-version: '3.11'
26 | - name: Get ghidra
27 | run: |
28 | curl -L https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_${GHIDRA_VERSION}_build/ghidra_${GHIDRA_VERSION}_PUBLIC_${GHIDRA_DATE}.zip -o /tmp/ghidra.zip
29 | unzip -d /tmp /tmp/ghidra.zip
30 | echo /tmp/ghidra_*/support >> $GITHUB_PATH
31 | - name: Update mapping
32 | run: |
33 | echo "$GHIDRA_SSH_AUTH" > ssh_key
34 | python scripts/update_mapping.py --username github-action --ssh-key ssh_key --program 'th06_102h.exe' 'ghidra://roblab.la/Touhou/Touhou 06 - The Embodiment of Scarlet Devil'
35 | rm ssh_key
36 | env: # Or as an environment variable
37 | GHIDRA_SSH_AUTH: ${{ secrets.GHIDRA_SSH_AUTH }}
38 | - name: Create PR to TH06 with updated mapping
39 | uses: peter-evans/create-pull-request@v6
40 | with:
41 | commit-message: Update mapping to latest ghidra changes
42 | branch: update-mapping
43 | title: Update mapping from ghidra
44 | body: Updates the mapping to the latest changes in the ghidra database.
45 | token: ${{ secrets.GH_TOKEN_PAT }}
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | diff
3 | *.obj
4 | *.exe
5 | resources/original.ico
6 | log.txt
7 | scripts/dls
8 | scripts/prefix
9 | build.ninja
10 | __pycache__
11 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "3rdparty/munit"]
2 | path = 3rdparty/munit
3 | url = https://github.com/roblabla/munit
4 | branch = msvc2002
5 | [submodule "3rdparty/Detours"]
6 | path = 3rdparty/Detours
7 | url = https://github.com/roblabla/Detours
8 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/mirrors-clang-format
3 | rev: 'v18.1.8'
4 | hooks:
5 | - id: clang-format
6 | - repo: https://github.com/astral-sh/ruff-pre-commit
7 | # Ruff version.
8 | rev: v0.5.1
9 | hooks:
10 | # Run the linter.
11 | - id: ruff
12 | # Run the formatter.
13 | - id: ruff-format
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 東方紅魔郷 ~ the Embodiment of Scarlet Devil
2 |
3 |
4 |
5 |
6 |
7 |
8 | [![Discord][discord-badge]][discord] <- click here to join discord server.
9 |
10 | [discord]: https://discord.gg/VyGwAjrh9a
11 | [discord-badge]: https://img.shields.io/discord/1147558514840064030?color=%237289DA&logo=discord&logoColor=%23FFFFFF
12 |
13 | This project aims to perfectly reconstruct the source code of [Touhou Koumakyou ~ the Embodiment of Scarlet Devil 1.02h](https://en.touhouwiki.net/wiki/Embodiment_of_Scarlet_Devil) by Team Shanghai Alice.
14 |
15 | **This project is still highly work in progress and in its early stages.**
16 |
17 |
18 | ## Installation
19 |
20 | ### Executable
21 |
22 | This project requires the original `東方紅魔郷.exe` version 1.02h (SHA256 hashsum 9f76483c46256804792399296619c1274363c31cd8f1775fafb55106fb852245, you can check hashsum on windows with command `certutil -hashfile SHA256`.)
23 |
24 | Copy `東方紅魔郷.exe` to `resources/game.exe`.
25 |
26 | ### Dependencies
27 |
28 | The build system has the following package requirements:
29 |
30 | - `python3` >= 3.4
31 | - `msiextract` (On linux/macos only)
32 | - `wine` (on linux/macos only, prefer CrossOver on macOS to avoid possible CL.EXE heap issues)
33 | - `aria2c` (optional, allows for torrent downloads, will automatically install on Windows if selected.)
34 |
35 | The rest of the build system is constructed out of Visual Studio 2002 and DirectX 8.0 from the Web Archive.
36 |
37 | #### Configure devenv
38 |
39 | This will download and install compiler, libraries, and other tools.
40 |
41 | If you are on windows, and for some reason want to download dependencies manually,
42 | run this command to get the list of files to download:
43 |
44 | ```
45 | python scripts/create_devenv.py scripts/dls scripts/prefix --no-download
46 | ```
47 |
48 | But if you want everything to be downloaded automatically, run it like this instead:
49 |
50 | ```
51 | python scripts/create_devenv.py scripts/dls scripts/prefix
52 | ```
53 |
54 | And if you want to use torrent to download those dependencies, use this:
55 |
56 | ```
57 | python scripts/create_devenv.py scripts/dls scripts/prefix --torrent
58 | ```
59 |
60 | On linux and mac, run the following script:
61 | ```bash
62 | # NOTE: On macOS if you use CrossOver.
63 | # export WINE=/wine
64 | ./scripts/create_th06_prefix
65 | ```
66 |
67 | #### Building
68 |
69 | Run the following script:
70 |
71 | ```
72 | python3 ./scripts/build.py
73 | ```
74 |
75 | This will automatically generate a ninja build script `build.ninja`, and run
76 | ninja on it.
77 |
78 | ## Contributing
79 |
80 | ### Reverse Engineering
81 |
82 | You can find an XML export of our Ghidra RE in the companion repository
83 | [th06-re], in the `xml` branch. This repo is updated nightly through
84 | [`scripts/export_ghidra_database.py`], and its history matches the checkin
85 | history from our team's Ghidra Server.
86 |
87 | If you wish to help us in our Reverse Engineering effort, please contact
88 | @roblabla on discord so we can give you an account on the Ghidra Server.
89 |
90 | ### Reimplementation
91 |
92 | The easiest way to work on the reimplementation is through the use of
93 | [`objdiff`](https://github.com/encounter/objdiff). Here's how to get started:
94 |
95 | 1. First, follow the instruction above to get a devenv setup.
96 | 1. Copy the original `東方紅魔郷.exe` file (version 1.02h) to the
97 | `resources/` folder, and rename it into `game.exe`. This will be used as the source to compare the
98 | reimplementations against.
99 | 1. Download the latest version of objdiff.
100 | 1. Run `python3 scripts/export_ghidra_objs.py --import-csv`. This will extract
101 | from `resources/game.exe` the object files that objdiff can compare against.
102 | 1. Finally, run objdiff and open the th06 project.
103 |
104 | #### Choosing a function to decompile
105 |
106 | The easiest is to look at the `config/stubbed.csv` files. Those are all
107 | functions that are automatically stubbed out. You should pick one of them, open
108 | the associated object file in objdiff, and click on the function of interest.
109 |
110 | Then, open the correct `cpp` file, copy/paste the declaration, and start
111 | hacking! It may be useful to take the ghidra decompiler output as a base. You
112 | can find this output in the [th06-re] repository.
113 |
114 | # Credits
115 |
116 | We would like to extend our thanks to the following individuals for their
117 | invaluable contributions:
118 |
119 | - @EstexNT for porting the [`var_order` pragma](scripts/pragma_var_order.cpp) to
120 | MSVC7.
121 |
122 | [th06-re]: https://github.com/happyhavoc/th06-re
123 |
--------------------------------------------------------------------------------
/config/ghidra-user-maps.toml:
--------------------------------------------------------------------------------
1 | mary = "Mary "
2 | roblabla = "roblabla "
3 | wearr = "wearrrrr "
4 | VieRo = "PieVieRo "
5 | reisen = "[[Reisen]] <95779166+0x32767@users.noreply.github.com>"
6 | renzo904 = "Renzo904 <63516530+Renzo904@users.noreply.github.com>"
7 |
--------------------------------------------------------------------------------
/config/ghidra_ns_to_obj.csv:
--------------------------------------------------------------------------------
1 | AnmManager,th06::AnmVm,th06::AnmManager
2 | AsciiManager,th06::AsciiManager,th06::StageMenu
3 | BombData,th06::BombData
4 | BulletManager,th06::BulletManager
5 | Chain,th06::Chain,th06::ChainElem
6 | CMyFont,th06::CMyFont
7 | Controller,th06::Controller
8 | EclManager,th06::EclManager
9 | EffectManager,th06::EffectManager
10 | Ending,th06::Ending
11 | EnemyEclInstr,th06::EnemyEclInstr
12 | EnemyManager,th06::EnemyManager,th06::Enemy
13 | FileAbstraction,th06::FileAbstraction
14 | FileSystem,th06::FileSystem
15 | GameErrorContext,th06::GameErrorContext
16 | GameManager,th06::GameManager
17 | GameWindow,th06::GameWindow
18 | Gui,th06::Gui,th06::GuiImpl
19 | IPbg3Parser,th06::IPbg3Parser
20 | ItemManager,th06::ItemManager
21 | MainMenu,th06::MainMenu
22 | MidiOutput,th06::MidiDevice,th06::MidiTimer,th06::MidiOutput
23 | MusicRoom,th06::MusicRoom
24 | Pbg3Archive,th06::Pbg3Archive
25 | Pbg3Parser,th06::Pbg3Parser
26 | Player,th06::Player
27 | ReplayManager,th06::ReplayManager
28 | ResultScreen,th06::ResultScreen
29 | Rng,th06::Rng
30 | ScreenEffect,th06::ScreenEffect
31 | SoundPlayer,th06::SoundPlayer
32 | Stage,th06::Stage,th06::AnmManager::ExecuteAnmIdx
33 | Supervisor,th06::Supervisor
34 | TextHelper,th06::TextHelper
35 | utils,th06::utils
36 | ZunTimer,th06::ZunTimer
37 | zwave,th06::CSound,th06::CSoundManager,th06::CStreamingSound,th06::CWaveFile
38 | main,WinMain
39 |
--------------------------------------------------------------------------------
/config/order.txt:
--------------------------------------------------------------------------------
1 | ??0AsciiManager@th06@@QAE@XZ
2 | ??0StageMenu@th06@@QAE@XZ
3 | ?OnUpdate@AsciiManager@th06@@SA?AW4ChainCallbackResult@2@PAU12@@Z
4 | ?OnDrawMenus@AsciiManager@th06@@SA?AW4ChainCallbackResult@2@PAU12@@Z
5 | ?OnDrawPopups@AsciiManager@th06@@SA?AW4ChainCallbackResult@2@PAU12@@Z
6 | ?RegisterChain@AsciiManager@th06@@SA?AW4ZunResult@@XZ
7 | ?AddedCallback@AsciiManager@th06@@SA?AW4ZunResult@@PAU12@@Z
8 | ?InitializeVms@AsciiManager@th06@@QAEXXZ
9 | ?DeletedCallback@AsciiManager@th06@@SA?AW4ZunResult@@PAU12@@Z
10 | ?CutChain@AsciiManager@th06@@SAXXZ
11 | ?AddString@AsciiManager@th06@@QAEXPAUD3DXVECTOR3@@PAD@Z
12 | ?AddFormatText@AsciiManager@th06@@QAAXPAUD3DXVECTOR3@@PBDZZ
13 | ?DrawStrings@AsciiManager@th06@@QAEXXZ
14 | ?CreatePopup1@AsciiManager@th06@@QAEXPAUD3DXVECTOR3@@HK@Z
15 | ?CreatePopup2@AsciiManager@th06@@QAEXPAUD3DXVECTOR3@@HK@Z
16 | ?OnUpdateGameMenu@StageMenu@th06@@QAEHXZ
17 | ?OnDrawGameMenu@StageMenu@th06@@QAEXXZ
18 | ?OnUpdateRetryMenu@StageMenu@th06@@QAEHXZ
19 | ?OnDrawRetryMenu@StageMenu@th06@@QAEXXZ
20 | ?DrawPopupsWithHwVertexProcessing@AsciiManager@th06@@QAEXXZ
21 | ?DrawPopupsWithoutHwVertexProcessing@AsciiManager@th06@@QAEXXZ
22 | ?Initialize@AnmVm@th06@@QAEXXZ
23 | ?D3DXMatrixIdentity@@YAPAUD3DXMATRIX@@PAU1@@Z
24 | ??0AnmVm@th06@@QAE@XZ
25 | ??0Stage@th06@@QAE@XZ
26 | ?OnUpdate@Stage@th06@@SA?AW4ChainCallbackResult@2@PAU12@@Z
27 | ?OnDrawHighPrio@Stage@th06@@SA?AW4ChainCallbackResult@2@PAU12@@Z
28 | ?OnDrawLowPrio@Stage@th06@@SA?AW4ChainCallbackResult@2@PAU12@@Z
29 | ?AddedCallback@Stage@th06@@SA?AW4ZunResult@@PAU12@@Z
30 | ?RegisterChain@Stage@th06@@SA?AW4ZunResult@@I@Z
31 | ?DeletedCallback@Stage@th06@@SA?AW4ZunResult@@PAU12@@Z
32 | ?CutChain@Stage@th06@@SAXXZ
33 | ?LoadStageData@Stage@th06@@QAE?AW4ZunResult@@PAD0@Z
34 | ?UpdateObjects@Stage@th06@@QAE?AW4ZunResult@@XZ
35 | ?RenderObjects@Stage@th06@@QAE?AW4ZunResult@@H@Z
36 | ?ExecuteAnmIdx@AnmManager@th06@@QAEXPAUAnmVm@2@H@Z
37 | ?BombReimuACalc@BombData@th06@@SAXPAUPlayer@2@@Z
38 | ?BombReimuADraw@BombData@th06@@SAXPAUPlayer@2@@Z
39 | ?DarkenViewport@BombData@th06@@SAXPAUPlayer@2@@Z
40 | ?BombReimuBCalc@BombData@th06@@SAXPAUPlayer@2@@Z
41 | ?BombReimuBDraw@BombData@th06@@SAXPAUPlayer@2@@Z
42 | ?BombMarisaACalc@BombData@th06@@SAXPAUPlayer@2@@Z
43 | ?BombMarisaADraw@BombData@th06@@SAXPAUPlayer@2@@Z
44 | ?BombMarisaBCalc@BombData@th06@@SAXPAUPlayer@2@@Z
45 | ?BombMarisaBDraw@BombData@th06@@SAXPAUPlayer@2@@Z
46 |
--------------------------------------------------------------------------------
/config/stubbed.csv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happyhavoc/th06/ca1d00894bc701e6b134cd37cf1d34540cafb921/config/stubbed.csv
--------------------------------------------------------------------------------
/resources/placeholder.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happyhavoc/th06/ca1d00894bc701e6b134cd37cf1d34540cafb921/resources/placeholder.ico
--------------------------------------------------------------------------------
/resources/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happyhavoc/th06/ca1d00894bc701e6b134cd37cf1d34540cafb921/resources/placeholder.png
--------------------------------------------------------------------------------
/resources/progress_template.svg:
--------------------------------------------------------------------------------
1 |
77 |
78 |
--------------------------------------------------------------------------------
/resources/th06.rc:
--------------------------------------------------------------------------------
1 | 105 ICON DISCARDABLE "..\build\icon.ico"
2 |
--------------------------------------------------------------------------------
/scripts/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:noble AS base
2 |
3 | # Install wine and msitools
4 | RUN dpkg --add-architecture i386 && \
5 | mkdir -pm755 /etc/apt/keyrings && \
6 | apt-get update && apt-get install -y wget gpg && \
7 | wget -O - https://dl.winehq.org/wine-builds/winehq.key | gpg --dearmor -o /etc/apt/keyrings/winehq-archive.key - && \
8 | wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/noble/winehq-noble.sources && \
9 | apt-get update && apt-get install -y --install-recommends winehq-stable msitools git && \
10 | apt-get remove --autoremove -y wget gpg && apt-get clean && rm -rf /var/lib/apt/lists/*
11 |
12 | FROM base AS build
13 | COPY create_devenv.py /tmp/create_devenv.py
14 | COPY winhelpers.py /tmp/winhelpers.py
15 | COPY th06run.bat /tmp/th06run.bat
16 | COPY th06vars.bat /tmp/th06vars.bat
17 | COPY pragma_var_order.cpp /tmp/pragma_var_order.cpp
18 | RUN python3 /tmp/create_devenv.py /tmp/dls /th06_prefix
19 |
20 | FROM base AS final
21 | COPY --from=build /th06_prefix /th06_prefix
22 | ENV TH06_DEVENV_PREFIX=/th06_prefix
23 |
--------------------------------------------------------------------------------
/scripts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happyhavoc/th06/ca1d00894bc701e6b134cd37cf1d34540cafb921/scripts/__init__.py
--------------------------------------------------------------------------------
/scripts/build.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | from pathlib import Path
3 | import textwrap
4 |
5 | from configure import BuildType, configure
6 | from winhelpers import run_windows_program
7 |
8 | SCRIPTS_DIR = Path(__file__).parent
9 |
10 |
11 | def build(build_type, verbose=False, jobs=1, target=None):
12 | configure(build_type)
13 |
14 | ninja_args = []
15 | if verbose:
16 | ninja_args += ["-v"]
17 |
18 | if jobs != 0:
19 | ninja_args += ["-j" + str(jobs)]
20 |
21 | if target is not None:
22 | ninja_args += [target]
23 | elif build_type == BuildType.TESTS:
24 | ninja_args += ["build/th06e-tests.exe"]
25 | elif build_type == BuildType.DLLBUILD:
26 | ninja_args += ["build/th06e.dll"]
27 | elif build_type == BuildType.OBJDIFFBUILD:
28 | ninja_args += ["objdiff"]
29 | else:
30 | ninja_args += ["build/th06e.exe"]
31 |
32 | # Then, run the build. We use run_windows_program to automatically go through
33 | # wine if running on linux/macos. scripts/th06run.bat will setup PATH and other
34 | # environment variables for the MSVC toolchain to work before calling ninja.
35 | run_windows_program(
36 | [str(SCRIPTS_DIR / "th06run.bat"), "ninja"] + ninja_args,
37 | cwd=str(SCRIPTS_DIR.parent),
38 | )
39 |
40 |
41 | def main():
42 | parser = argparse.ArgumentParser(
43 | "th06-build", formatter_class=argparse.RawTextHelpFormatter
44 | )
45 | parser.add_argument(
46 | "--build-type",
47 | choices=[
48 | "normal",
49 | "diffbuild",
50 | "tests",
51 | "dllbuild",
52 | "objdiffbuild",
53 | "binary_matchbuild",
54 | ],
55 | default="normal",
56 | )
57 | parser.add_argument(
58 | "-j",
59 | "--jobs",
60 | type=int,
61 | default=1,
62 | help=textwrap.dedent("""
63 | Number of jobs to run in parallel. Set to 0 to run one job per CPU core. Defaults to 1.
64 | Note that parallel builds may not work when running through wine.
65 | See https://github.com/happyhavoc/th06/issues/79 for more information."""),
66 | )
67 | parser.add_argument("--verbose", action="store_true")
68 | parser.add_argument("--object-name", required=False)
69 | parser.add_argument(
70 | "target",
71 | nargs="?",
72 | help=textwrap.dedent("""
73 | Ninja target to build. Default depends on the build type:
74 | - Normal and diff builds will build th06e.exe
75 | - dll builds will build th06e.dll
76 | - Test builds will build th06e-tests.exe
77 | - objdiff builds will build all the object files necessary for objdiff.
78 | """),
79 | )
80 | args = parser.parse_args()
81 | target = None
82 |
83 | # First, create the build.ninja file that will be used to build.
84 | if args.build_type == "normal":
85 | build_type = BuildType.NORMAL
86 | elif args.build_type == "diffbuild":
87 | build_type = BuildType.DIFFBUILD
88 | elif args.build_type == "tests":
89 | build_type = BuildType.TESTS
90 | elif args.build_type == "dllbuild":
91 | build_type = BuildType.DLLBUILD
92 | elif args.build_type == "objdiffbuild":
93 | build_type = BuildType.OBJDIFFBUILD
94 | elif args.build_type == "binary_matchbuild":
95 | build_type = BuildType.BINARY_MATCHBUILD
96 |
97 | if args.object_name is not None:
98 | object_name = Path(args.object_name).name
99 | target = "build/objdiff/reimpl/" + object_name
100 | elif args.target is not None:
101 | target = args.target
102 |
103 | build(build_type, args.verbose, args.jobs, target=target)
104 |
105 |
106 | if __name__ == "__main__":
107 | main()
108 |
--------------------------------------------------------------------------------
/scripts/create_th06_prefix:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6 |
7 | PREFIX_PATH=$SCRIPT_DIR/prefix
8 |
9 | echo "Creating devenv in $PREFIX_PATH"
10 | # $@ is used to pass all arguments to the python script
11 | python3 "$SCRIPT_DIR/create_devenv.py" "$SCRIPT_DIR/dls" "$PREFIX_PATH" $@
12 |
13 | echo "DONE"
14 |
--------------------------------------------------------------------------------
/scripts/diff_all_functions.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import argparse
4 | import csv
5 | from pathlib import Path
6 | import subprocess
7 | import sys
8 | import traceback
9 |
10 | from generate_function_diff import (
11 | generate_function_diff_objdiff,
12 | generate_function_diff_satsuki,
13 | )
14 |
15 |
16 | def main():
17 | parser = argparse.ArgumentParser(
18 | prog="diff_all_functions",
19 | description="Generate a diff report of all implemented functions.",
20 | )
21 | parser.add_argument(
22 | "--diff-method",
23 | action="store",
24 | choices=["satsuki", "objdiff"],
25 | default="satsuki",
26 | help="Which program to use to generate the diff.",
27 | )
28 | args = parser.parse_args()
29 |
30 | base_dir = Path(__file__).parent.parent
31 | config_dir = base_dir / "config"
32 |
33 | implemented_csv = config_dir / "implemented.csv"
34 |
35 | success = True
36 |
37 | with open(implemented_csv) as f:
38 | vals = []
39 | for row in csv.reader(f):
40 | try:
41 | if args.diff_method == "satsuki":
42 | left, right, diff, ratio = generate_function_diff_satsuki(row[0])
43 | else:
44 | left, right, diff, ratio = generate_function_diff_objdiff(row[0])
45 | vals.append(
46 | {
47 | "name": row[0],
48 | "diff": diff,
49 | "ratio": ratio,
50 | }
51 | )
52 | except subprocess.CalledProcessError as e:
53 | success = False
54 | if e.stderr is not None:
55 | vals.append(
56 | {"name": row[0], "error": str(e.stderr, "utf8").strip()}
57 | )
58 | else:
59 | vals.append({"name": row[0], "error": "failed"})
60 | except Exception as e:
61 | vals.append(
62 | {
63 | "name": row[0],
64 | "error": "".join(
65 | traceback.format_exception(None, e, e.__traceback__)
66 | ),
67 | }
68 | )
69 |
70 | print("# Report")
71 | print("")
72 | print("name | result")
73 | print("-----|-------")
74 | for val in vals:
75 | name = val["name"]
76 | id = val["name"].lower().replace(":", "__")
77 | if "error" in val:
78 | name = f"[{name}](#user-content-{id})"
79 | sys.stdout.flush()
80 | sys.stdout.buffer.write(f"{name} | 💥\n".encode("utf8"))
81 | else:
82 | if val["ratio"] != 1:
83 | name = f"[{name}](#user-content-{id})"
84 | print(f"{name} | {val['ratio'] * 100:.2f}%")
85 |
86 | for val in vals:
87 | if "error" not in val and val["ratio"] == 1:
88 | # 100% matching, nothing to see here.
89 | continue
90 |
91 | print("")
92 | print("")
93 | id = val["name"].lower().replace(":", "__")
94 | print(f'{val["name"]}
')
95 | print("")
96 | if "error" in val:
97 | print("Failed to generate diff:")
98 | print(val["error"])
99 | elif val["ratio"] != 1:
100 | print("```diff")
101 | print(val["diff"])
102 | print("```")
103 | print("")
104 | print(" ")
105 |
106 | if not success:
107 | sys.exit(1)
108 |
109 |
110 | if __name__ == "__main__":
111 | main()
112 |
--------------------------------------------------------------------------------
/scripts/export_ghidra_objs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env nix-shell
2 | #!nix-shell -p python311 -i python3
3 |
4 | import argparse
5 | import os
6 | from pathlib import Path
7 | import tempfile
8 | import urllib.request
9 |
10 | import ghidra_helpers
11 |
12 | SCRIPT_PATH = Path(os.path.realpath(__file__)).parent
13 |
14 |
15 | def main():
16 | parser = argparse.ArgumentParser(
17 | description="Export a ghidra database history to git",
18 | )
19 | parser.add_argument("--local-project-dir", help="Path to the local ghidra project")
20 | parser.add_argument("--local-project-name", help="Path to the local ghidra project")
21 | parser.add_argument("--import-xml", action="store_true", help="Use the XML export")
22 | parser.add_argument(
23 | "--import-csv", action="store_true", help="Use the mapping.csv file"
24 | )
25 | parser.add_argument("--program", help="Program to export", default="th06_102h.exe")
26 | args = parser.parse_args()
27 |
28 | os.makedirs(str(SCRIPT_PATH.parent / "build" / "objdiff" / "orig"), exist_ok=True)
29 |
30 | if args.import_xml:
31 | with tempfile.TemporaryDirectory() as tempdir:
32 | filename, _ = urllib.request.urlretrieve(
33 | "https://raw.githubusercontent.com/happyhavoc/th06-re/xml/th06_102h.exe.xml"
34 | )
35 | ghidra_helpers.runAnalyze(
36 | str(tempdir),
37 | "Touhou 06",
38 | import_file=str(SCRIPT_PATH.parent / "resources" / "game.exe"),
39 | analysis=True,
40 | post_scripts=[
41 | ["ImportFromXml.java", filename],
42 | [
43 | "ExportDelinker.java",
44 | str(SCRIPT_PATH.parent / "config" / "ghidra_ns_to_obj.csv"),
45 | str(SCRIPT_PATH.parent / "build" / "objdiff" / "orig"),
46 | ],
47 | ],
48 | )
49 | elif args.import_csv:
50 | with tempfile.TemporaryDirectory() as tempdir:
51 | mapping_csv = SCRIPT_PATH.parent / "config" / "mapping.csv"
52 | ghidra_helpers.runAnalyze(
53 | str(tempdir),
54 | "Touhou 06",
55 | import_file=str(SCRIPT_PATH.parent / "resources" / "game.exe"),
56 | analysis=True,
57 | post_scripts=[
58 | ["ImportFromCsv.java", str(mapping_csv)],
59 | [
60 | "ExportDelinker.java",
61 | str(SCRIPT_PATH.parent / "config" / "ghidra_ns_to_obj.csv"),
62 | str(SCRIPT_PATH.parent / "build" / "objdiff" / "orig"),
63 | ],
64 | ],
65 | )
66 | else:
67 | repo = args.local_project_dir
68 | project_name = args.local_project_name
69 | program = args.program
70 |
71 | ghidra_helpers.runAnalyze(
72 | repo,
73 | project_name,
74 | process=program,
75 | pre_scripts=[
76 | [
77 | "ExportDelinker.java",
78 | str(SCRIPT_PATH.parent / "config" / "ghidra_ns_to_obj.csv"),
79 | str(SCRIPT_PATH.parent / "build" / "objdiff" / "orig"),
80 | ]
81 | ],
82 | )
83 |
84 |
85 | if __name__ == "__main__":
86 | main()
87 |
--------------------------------------------------------------------------------
/scripts/extract_icon.py:
--------------------------------------------------------------------------------
1 | import icon_extractor
2 | from pathlib import Path
3 | import sys
4 | import os
5 | import argparse
6 |
7 |
8 | SCRIPT_PATH = Path(os.path.realpath(__file__)).parent
9 | RESOURCES_PATH = SCRIPT_PATH.parent / "resources"
10 | FILENAME = RESOURCES_PATH / "game.exe"
11 |
12 |
13 | parser = argparse.ArgumentParser(
14 | prog="extract_icon", description="Extract the original icon from the game."
15 | )
16 | parser.add_argument(
17 | "-o", "--output", required=True, help="Path to write the extracted icon"
18 | )
19 | args = parser.parse_args()
20 |
21 | if not FILENAME.exists():
22 | sys.stderr.write(
23 | "extract_icon.py: 'game.exe' not found. Copy your executable of Touhou 06 to 'resources/game.exe'"
24 | )
25 | sys.exit(1)
26 | icon = icon_extractor.ExtractIcon(str(FILENAME))
27 |
28 | with open(str(args.output), "wb") as icon_file:
29 | entries = icon.get_group_icons()
30 | icon_file.write(icon.export_raw(entries[0], 0))
31 |
--------------------------------------------------------------------------------
/scripts/gendef.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import itertools
3 | import string
4 | import subprocess
5 | import sys
6 |
7 | parser = argparse.ArgumentParser(
8 | prog="gendef", description="Generate .def file based on the provided object files."
9 | )
10 | parser.add_argument(
11 | "-o", "--output", action="store", help="File to store the generated stubs in"
12 | )
13 | parser.add_argument("input", nargs="+", help="File to store the generated stubs in")
14 | args = parser.parse_args()
15 |
16 | f = open(args.output, "w")
17 | print("EXPORTS", file=f)
18 |
19 | for arg in args.input:
20 | out = subprocess.check_output(["dumpbin", "/HEADERS", "/SYMBOLS", arg])
21 |
22 | # First, find the appropriate section
23 | cur_section = None
24 | text_sections = []
25 | for line in out.split(b"\n"):
26 | if line.startswith(b"SECTION HEADER #"):
27 | cur_section = int(line[len("SECTION HEADER #") :], 16)
28 | if b".text" in line:
29 | text_sections.append(cur_section)
30 |
31 | if len(text_sections) == 0:
32 | print("WARNING: Failed to find .text in object file " + arg, file=sys.stderr)
33 |
34 | for line in out.split(b"\n"):
35 | line = str(line, "utf8")
36 | if "External" in line:
37 | sect_idx = line.find("SECT")
38 |
39 | def ishexdigit(c):
40 | return c in string.hexdigits
41 |
42 | if sect_idx == -1:
43 | continue
44 | sect_int = int(
45 | "".join(
46 | itertools.takewhile(ishexdigit, line[sect_idx + len("SECT") :])
47 | ),
48 | 16,
49 | )
50 | if sect_int in text_sections:
51 | symbols = line.split("| ")[1].strip()
52 | symbols = symbols.split(" ", 1)
53 | if len(symbols) > 1:
54 | mangled_symbol, demangled_symbol = symbols
55 | else:
56 | mangled_symbol = symbols[0]
57 | demangled_symbol = ""
58 | print(" " + mangled_symbol + " PRIVATE", file=f)
59 |
--------------------------------------------------------------------------------
/scripts/generate_decompme_toolchain.py:
--------------------------------------------------------------------------------
1 | # Generates a toolchain compatible with decompme's win9x support[0] from a
2 | # prefix generated with create_th06_prefix
3 | #
4 | # [0]: https://github.com/decompme/decomp.me/pull/802
5 | import os
6 | import shutil
7 | from pathlib import Path
8 |
9 |
10 | def main():
11 | script_path = Path(os.path.dirname(os.path.realpath(__file__)))
12 |
13 | prefix_path = script_path / "prefix"
14 |
15 | vc7_path = prefix_path / "PROGRAM FILES" / "MICROSOFT VISUAL STUDIO .NET" / "VC7"
16 | ide_path = (
17 | prefix_path
18 | / "PROGRAM FILES"
19 | / "MICROSOFT VISUAL STUDIO .NET"
20 | / "COMMON7"
21 | / "IDE"
22 | )
23 | dx8sdk_path = prefix_path / "mssdk"
24 |
25 | decompme_toolchain = script_path / "msvc70"
26 |
27 | decompme_toolchain.mkdir()
28 |
29 | # First, create bin directory
30 | decompme_bin = decompme_toolchain / "Bin"
31 | decompme_bin.mkdir()
32 | # Copy all the programs in vc7_path bin to our bin. Lowercase them.
33 | vc7_bin = vc7_path / "BIN"
34 | for src_file in vc7_bin.glob("*"):
35 | if not src_file.is_file():
36 | continue
37 |
38 | dst_file = decompme_bin / src_file.relative_to(vc7_bin)
39 | dst_file = dst_file.parent / dst_file.name.lower()
40 |
41 | print(src_file, "->", dst_file)
42 | shutil.copyfile(src_file, dst_file)
43 |
44 | # Copy a handful of necessary dlls from ide_path to our bin
45 | shutil.copyfile(ide_path / "MSPDB70.DLL", decompme_bin / "mspdb70.dll")
46 | shutil.copyfile(ide_path / "MSOBJ10.DLL", decompme_bin / "msobj10.dll")
47 |
48 | # Next, create the Include directory
49 | decompme_include = decompme_toolchain / "Include"
50 | decompme_include.mkdir()
51 |
52 | # Start with VC7 includes.
53 | vc7_include = vc7_path / "INCLUDE"
54 | for src_file in vc7_include.rglob("*"):
55 | if not src_file.is_file():
56 | continue
57 |
58 | dst_file = decompme_include / src_file.relative_to(vc7_include)
59 | dst_file = dst_file.parent / dst_file.name.upper()
60 |
61 | # First, ensure parent directory exists
62 | dst_file.parent.mkdir(exist_ok=True)
63 |
64 | # Then, copy the file.
65 | shutil.copyfile(src_file, dst_file)
66 |
67 | # Then, copy the PlatformSDK
68 | platform_sdk_include = vc7_path / "PlatformSDK" / "Include"
69 | for src_file in platform_sdk_include.rglob("*"):
70 | if not src_file.is_file():
71 | continue
72 |
73 | dst_file = decompme_include / src_file.relative_to(platform_sdk_include)
74 | dst_file = dst_file.parent / dst_file.name.upper()
75 |
76 | # First, ensure parent directory exists
77 | dst_file.parent.mkdir(exist_ok=True)
78 |
79 | # Then, copy the file.
80 | shutil.copyfile(src_file, dst_file)
81 |
82 | # Finally, copy DXSDK
83 | dx8sdk_include = dx8sdk_path / "include"
84 | for src_file in dx8sdk_include.rglob("*"):
85 | if not src_file.is_file():
86 | continue
87 |
88 | dst_file = decompme_include / src_file.relative_to(dx8sdk_include)
89 | dst_file = dst_file.parent / dst_file.name.upper()
90 |
91 | # First, ensure parent directory exists
92 | dst_file.parent.mkdir(exist_ok=True)
93 |
94 | # Then, copy the file.
95 | shutil.copyfile(src_file, dst_file)
96 |
97 |
98 | if __name__ == "__main__":
99 | main()
100 |
--------------------------------------------------------------------------------
/scripts/generate_detours.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import csv
3 | import sys
4 |
5 | parser = argparse.ArgumentParser(
6 | prog="generate_detours", description="Generate stubs based on the stubs.csv file."
7 | )
8 | parser.add_argument(
9 | "-o", "--output", action="store", help="File to store the generated stubs in"
10 | )
11 | parser.add_argument(
12 | "-i", "--input-def", action="store", help="Def file to find mangled symbols in"
13 | )
14 | args = parser.parse_args()
15 |
16 |
17 | def get_path_of_mangled_symbol(symbol):
18 | if symbol[0] == "?":
19 | cpp_symbol = symbol[1:]
20 | path = cpp_symbol.split("@")
21 | last = next((idx for idx, x in enumerate(path) if x == ""), None)
22 | path = path[0:last]
23 |
24 | first_elem = path[0]
25 | if first_elem[0] == "?":
26 | if first_elem[1] == "0":
27 | cls = first_elem[2:]
28 | path[0] = cls + "::" + cls
29 | elif first_elem[1] == "1":
30 | cls = first_elem[2:]
31 | path[0] = cls + "::~" + cls
32 | elif first_elem[1:3] == "_H":
33 | return None
34 | else:
35 | print("WARNING: Unknown special symbol " + symbol)
36 |
37 | return "::".join(reversed(path))
38 | elif symbol[0] == "_":
39 | return symbol[1:].split("@", 1)[0]
40 | else:
41 | raise Exception("Unknown symbol kind " + symbol)
42 |
43 |
44 | fun_to_mangled_map = {}
45 | with open(args.input_def) as f:
46 | for line in f:
47 | if len(line.strip()) == 0:
48 | continue
49 |
50 | if line.strip() == "EXPORTS":
51 | continue
52 |
53 | mangled_symbol = line.rsplit(" ", 1)[0].strip()
54 | fun_path = get_path_of_mangled_symbol(mangled_symbol)
55 | if fun_path is None:
56 | continue
57 | print(fun_path)
58 | if (
59 | fun_path in fun_to_mangled_map
60 | and fun_to_mangled_map[fun_path] != mangled_symbol
61 | ):
62 | raise Exception("Overload detected, two functions patch " + fun_path)
63 | fun_to_mangled_map[fun_path] = mangled_symbol
64 |
65 | fun_to_mangled_map["operator_new"] = "??2@YAPAXI@Z"
66 | fun_to_mangled_map["_malloc"] = "malloc"
67 | fun_to_mangled_map["_calloc"] = "calloc"
68 | fun_to_mangled_map["_realloc"] = "realloc"
69 | fun_to_mangled_map["_free"] = "free"
70 | fun_to_mangled_map["__msize"] = "_msize"
71 |
72 | with open("config/mapping.csv") as f:
73 | mapping_csv = csv.reader(f)
74 | mapping_obj = {}
75 | for func in mapping_csv:
76 | fun_name = func[0]
77 | fun_addr = int(func[1], 16)
78 | mapping_obj[fun_name] = fun_addr
79 |
80 | detours = {}
81 | f = open("config/implemented.csv")
82 | implemented_csv = csv.reader(f)
83 |
84 | for implemented in implemented_csv:
85 | fun_name = implemented[0]
86 | fun_mangled_name = fun_to_mangled_map[fun_name]
87 | fun_addr = mapping_obj[fun_name]
88 | detours[fun_name] = {
89 | "fun_addr": fun_addr,
90 | "fun_mangled_name": fun_mangled_name,
91 | "stub": False,
92 | }
93 |
94 | f = open("config/stubbed.csv")
95 | stubbed_csv = csv.reader(f)
96 | for implemented in stubbed_csv:
97 | fun_name = implemented[0]
98 | fun_mangled_name = fun_to_mangled_map[fun_name]
99 | fun_addr = mapping_obj[fun_name]
100 | detours[fun_name] = {
101 | "fun_addr": fun_addr,
102 | "fun_mangled_name": fun_mangled_name,
103 | "stub": True,
104 | }
105 |
106 | # Add some necessary detouring to share MSVCRT heap with the main executable
107 | for fun_name in ["_malloc", "_calloc", "_realloc", "operator_new", "_free", "__msize"]:
108 | fun_mangled_name = fun_to_mangled_map[fun_name]
109 | fun_addr = mapping_obj[fun_name]
110 | detours[fun_name] = {
111 | "fun_addr": fun_addr,
112 | "fun_mangled_name": fun_mangled_name,
113 | "stub": False,
114 | }
115 |
116 | output = sys.stdout
117 | if args.output:
118 | output = open(args.output, "w")
119 |
120 | print("Detouring detours[] = {", file=output)
121 |
122 | first = True
123 | for detour in detours.values():
124 | if not first:
125 | print(",", file=output)
126 | first = False
127 | print(
128 | " { "
129 | + hex(detour["fun_addr"])
130 | + ', "'
131 | + detour["fun_mangled_name"]
132 | + '", '
133 | + str(detour["stub"]).upper()
134 | + " }",
135 | end="",
136 | file=output,
137 | )
138 |
139 | print("\n};", file=output)
140 |
--------------------------------------------------------------------------------
/scripts/generate_function_decompme.py:
--------------------------------------------------------------------------------
1 | from contextlib import suppress
2 | import json
3 | from pathlib import Path
4 | import re
5 | import subprocess
6 | import sys
7 | import urllib.error
8 | import urllib.request
9 |
10 | REPO_DIR = Path(__file__).parent.parent
11 | SCRIPTS_DIR = REPO_DIR / "scripts"
12 | CONFIG_DIR = REPO_DIR / "config"
13 | RESOURCES_DIR = REPO_DIR / "resources"
14 | INCLUDE_PATHS = [REPO_DIR / "src"]
15 |
16 |
17 | def cpp_expand(file, path, matched_files):
18 | out = ""
19 | for line in file:
20 | val = re.match(r'#include "(.*)"', line)
21 | if val:
22 | out += cpp_expand_path(val.group(1), matched_files)
23 | elif re.match(r"#pragma once", line):
24 | matched_files.add(path)
25 | else:
26 | out += line
27 | return out
28 |
29 |
30 | def cpp_expand_path(path, matched_files):
31 | # i18n.hpp is auto-generated by converting i18n.tpl to shift-jis. This might
32 | # cause issues, so let's just take the original utf8-encoded template file.
33 | if path == "i18n.hpp":
34 | path = "i18n.tpl"
35 |
36 | if path in matched_files:
37 | return ""
38 |
39 | for include_path in INCLUDE_PATHS:
40 | with suppress(FileNotFoundError):
41 | with open(include_path / path) as f:
42 | return cpp_expand(f, path, matched_files)
43 |
44 | raise Exception("No include found for " + path + " in " + str(INCLUDE_PATHS))
45 |
46 |
47 | def main():
48 | asm = subprocess.check_output(
49 | [
50 | str(SCRIPTS_DIR / "prefix" / "satsuki" / "satsuki"),
51 | "--mapping-file-csv",
52 | str(CONFIG_DIR / "mapping.csv"),
53 | "disassemble",
54 | "--att",
55 | "--force-address-zero",
56 | RESOURCES_DIR / "game.exe",
57 | sys.argv[1],
58 | ]
59 | ).decode("utf8")
60 | asm = ".att_syntax\n" + asm
61 | ctx = cpp_expand_path("th06.hpp", set())
62 | req = urllib.request.Request(
63 | "https://decomp.me/api/scratch",
64 | headers={
65 | "Content-Type": "application/json",
66 | },
67 | data=json.dumps(
68 | {
69 | "compiler": "msvc7.0",
70 | "compiler_flags": "/MT /EHsc /G5 /Gs /GS /Od /Oi /Ob1 /Op /TP",
71 | "context": ctx,
72 | "diff_flags": [],
73 | "diff_label": sys.argv[1],
74 | "libraries": [
75 | {"name": "directx", "version": "8.0"},
76 | ],
77 | "platform": "win32",
78 | "preset": 111,
79 | "target_asm": asm,
80 | }
81 | ).encode("utf8"),
82 | )
83 |
84 | try:
85 | with urllib.request.urlopen(req) as res:
86 | out_data = json.load(res)
87 | except urllib.error.HTTPError as err:
88 | print(json.load(err.fp))
89 | raise
90 |
91 | print(
92 | "https://decomp.me/scratch/"
93 | + out_data["slug"]
94 | + "/claim?token="
95 | + out_data["claim_token"]
96 | )
97 |
98 |
99 | if __name__ == "__main__":
100 | main()
101 |
--------------------------------------------------------------------------------
/scripts/generate_globals.py:
--------------------------------------------------------------------------------
1 | import csv
2 | import struct
3 | import sys
4 |
5 |
6 | def main():
7 | with open(sys.argv[1]) as f:
8 | globals_csv = csv.reader(f)
9 | globals_list = [
10 | (glob[0].strip().encode("utf8") + b"\0", int(glob[1].strip(), 16))
11 | for glob in globals_csv
12 | ]
13 |
14 | coff_file = struct.pack(
15 | "HHIIIHH",
16 | 0x014C, # machine - i386
17 | 0, # Number of sections - we have none
18 | 0, # Timestamp - unnecessary
19 | 0x14, # Pointer to symbol table. Goes right after the COFF header
20 | len(globals_list), # Number of symbols.
21 | 0, # Size of optional header. We don't have one.
22 | 5, # Characteristics. relocsStripped | lineNumsStripped
23 | )
24 |
25 | curStrTableOffset = 4
26 | for symbol, addr in globals_list:
27 | coff_file += struct.pack(
28 | "IIIHHBB",
29 | 0, # Put all names in the string table.
30 | curStrTableOffset, # offset in string table
31 | addr, # Address
32 | 0xFFFF, # SectionNumber - none of ours, we don't have one.
33 | 0, # Type - Null
34 | 2, # StorageClass - External
35 | 0, # NumOfAuxSymbols
36 | )
37 | curStrTableOffset += len(symbol)
38 |
39 | coff_file += struct.pack("I", curStrTableOffset)
40 | for symbol, _ in globals_list:
41 | coff_file += symbol
42 |
43 | with open(sys.argv[2], "wb") as f:
44 | f.write(coff_file)
45 |
46 |
47 | if __name__ == "__main__":
48 | main()
49 |
--------------------------------------------------------------------------------
/scripts/generate_objdiff_objs.py:
--------------------------------------------------------------------------------
1 | import coff
2 | import csv
3 | from pathlib import Path
4 | import struct
5 | import sys
6 |
7 | SCRIPTS_DIR = Path(__file__).parent
8 |
9 |
10 | def demangle_msvc(sym):
11 | if len(sym) == 0:
12 | return sym
13 |
14 | if sym[0:1] == b"_":
15 | # Handle stdcall symbols first, those are simple. First remove the leading
16 | # underscore, then split on the last @ and remove everything that comes afterwards
17 | end_of_sym = sym.rfind(b"@")
18 | if end_of_sym == -1:
19 | # Not an stdcall, let's just not demangle it.
20 | return sym
21 | else:
22 | return sym[1:end_of_sym]
23 |
24 | if sym[0:1] != b"?":
25 | # Unmangled symbol?
26 | return sym
27 |
28 | # Handle CPP mangling.
29 | offset = 1
30 |
31 | # Read name. Start with special symbols
32 | special = None
33 | name = None
34 | if sym[offset : offset + 1] == b"?" and sym[offset + 1 : offset + 2].isdigit():
35 | # Special symbol.
36 | special = sym[offset + 1] - ord("0")
37 | offset += 2
38 | else:
39 | # Read a normal name.
40 | start_of_name = offset
41 | end_of_name = sym.find(b"@", offset)
42 | if end_of_name == -1:
43 | end_of_name = len(sym)
44 | offset = len(sym)
45 | else:
46 | offset = end_of_name + 1
47 | name = sym[start_of_name:end_of_name]
48 |
49 | # Read scope
50 | scope = []
51 | while True:
52 | start_of_scope = offset
53 | end_of_scope = sym.find(b"@", offset)
54 | if end_of_scope == -1:
55 | end_of_scope = len(sym)
56 | offset = len(sym)
57 | else:
58 | offset = end_of_scope + 1
59 | cur_scope = sym[start_of_scope:end_of_scope]
60 | if len(cur_scope) == 0:
61 | break
62 | scope.append(cur_scope)
63 |
64 | if name is not None:
65 | return b"::".join(scope[::-1]) + b"::" + name
66 | elif special == 0:
67 | return b"::".join(scope[::-1]) + b"::" + scope[0]
68 | elif special == 1:
69 | return b"::".join(scope[::-1]) + b"::~" + scope[0]
70 | else:
71 | return sym
72 |
73 |
74 | def sym_prefix(full_sym, prefix):
75 | return full_sym == prefix or full_sym.startswith(prefix + b"::")
76 |
77 |
78 | def rename_symbols(filename):
79 | reimpl_folder = SCRIPTS_DIR.parent / "build" / "objdiff" / "reimpl"
80 | orig_folder = SCRIPTS_DIR.parent / "build" / "objdiff" / "orig"
81 | config_folder = SCRIPTS_DIR.parent / "config"
82 |
83 | ns_to_obj = {}
84 |
85 | with open(str(config_folder / "ghidra_ns_to_obj.csv")) as f:
86 | ghidra_ns_to_obj = csv.reader(f)
87 | for vals in ghidra_ns_to_obj:
88 | ns_to_obj[vals[0]] = vals[1:]
89 |
90 | obj = coff.ObjectModule()
91 | with open(str(filename), "rb") as f:
92 | obj.unpack(f.read(), 0)
93 |
94 | # We filter to only the symbols with the namespace=filename, and we scrape everything but the function name
95 | seen = {}
96 | for sym_obj in obj.symbols:
97 | sym = sym_obj.get_name(obj.string_table)
98 | if seen.get(sym, False):
99 | continue
100 | seen[sym] = True
101 |
102 | demangled_sym = demangle_msvc(sym)
103 | if not any(
104 | sym_prefix(demangled_sym, val.encode("utf8"))
105 | for val in ns_to_obj[filename.stem]
106 | ):
107 | continue
108 |
109 | offset = obj.string_table.append(demangled_sym)
110 | sym_obj.name = b"\0\0\0\0" + struct.pack("I", offset)
111 |
112 | if not reimpl_folder.exists():
113 | reimpl_folder.mkdir(parents=True, exist_ok=True)
114 | orig_folder.mkdir(parents=True, exist_ok=True)
115 |
116 | with open(str(reimpl_folder / filename.name), "wb") as f:
117 | f.write(obj.get_buffer())
118 |
119 |
120 | if __name__ == "__main__":
121 | rename_symbols(Path(sys.argv[1]))
122 |
--------------------------------------------------------------------------------
/scripts/generate_stubs.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import csv
3 | import sys
4 |
5 | parser = argparse.ArgumentParser(
6 | prog="generate_stubs", description="Generate stubs based on the stubs.csv file."
7 | )
8 | parser.add_argument(
9 | "-o", "--output", action="store", help="File to store the generated stubs in"
10 | )
11 | args = parser.parse_args()
12 |
13 | with open("config/mapping.csv") as f:
14 | mapping_csv = csv.reader(f)
15 | mapping_obj = {}
16 | for func in mapping_csv:
17 | fun_name = func[0]
18 | fun_addr = int(func[1], 16)
19 | mapping_obj[fun_name] = {
20 | "fun_addr": int(func[1], 16),
21 | "fun_size": int(func[2], 16),
22 | "calling_convention": func[3],
23 | "varargs": func[4] == "varargs",
24 | "ret_type": func[5],
25 | "arg_types": func[6:],
26 | }
27 |
28 | with open("config/implemented.csv") as f:
29 | implemented_csv = csv.reader(f)
30 | for func in implemented_csv:
31 | mapping_obj[func[0]]["implemented"] = True
32 |
33 |
34 | f = open("config/stubbed.csv")
35 | stubbed_csv = csv.reader(f)
36 |
37 | output = sys.stdout
38 | if args.output:
39 | output = open(args.output, "w")
40 |
41 | ret_vals = {
42 | "void": "",
43 | "bool": "false",
44 | "u8": "0",
45 | "i8": "0",
46 | "u16": "0",
47 | "i16": "0",
48 | "short": "0",
49 | "unsigned short": "0",
50 | "u32": "0",
51 | "i32": "0",
52 | "unsigned int": "0",
53 | "int": "0",
54 | "unsigned long": "0",
55 | "long": "0",
56 | "f32": "0.0",
57 | "float": "0.0",
58 | "ZunResult": "ZUN_ERROR",
59 | "ChainCallbackResult": "CHAIN_CALLBACK_RESULT_EXIT_GAME_SUCCESS",
60 | "FireBulletResult": "FBR_STOP_SPAWNING",
61 | }
62 |
63 | for stub in stubbed_csv:
64 | fun_name = stub[0]
65 | fun = mapping_obj[fun_name]
66 | if fun.get("implemented", False):
67 | # We don't need to generate a stub for implemented functions.
68 | continue
69 |
70 | calling_convention = fun["calling_convention"]
71 | ret_type = fun["ret_type"]
72 | fun_name_parts = fun_name.split("::")
73 | if len(fun_name_parts) >= 2 and fun_name_parts[-1] == fun_name_parts[-2]:
74 | # Constructor
75 | ret_type = ""
76 | ret_val = ""
77 | elif len(fun_name_parts) >= 2 and fun_name_parts[-1] == "~" + fun_name_parts[-2]:
78 | # Destructor
79 | ret_type = ""
80 | ret_val = ""
81 | elif ret_type.endswith("*"):
82 | ret_val = "NULL"
83 | else:
84 | ret_val = ret_vals[ret_type]
85 | args_types = fun["arg_types"]
86 |
87 | if calling_convention == "__thiscall":
88 | this_type = args_types.pop(0)
89 |
90 | callconv = ""
91 | if calling_convention == "__stdcall":
92 | callconv = "__stdcall"
93 |
94 | fun_sig = ret_type + " " + callconv + " " + fun_name + "("
95 | fun_sig += ", ".join(
96 | [arg_type + " " + "a" + str(idx) for idx, arg_type in enumerate(args_types)]
97 | + (["..."] if fun["varargs"] else [])
98 | )
99 | fun_sig += ")"
100 | print(fun_sig + " {", file=output)
101 | print(' printf("STUBBED: ' + fun_sig + '\\n");', file=output)
102 | print(" return " + ret_val + ";", file=output)
103 | print("}", file=output)
104 |
--------------------------------------------------------------------------------
/scripts/ghidra/ExportDecomp.java:
--------------------------------------------------------------------------------
1 | /*
2 | * LICENSE
3 | */
4 | // Description
5 | //@author roblabla
6 | //@category exports
7 | //@keybinding
8 | //@menupath Skeleton
9 | //@toolbar Skeleton
10 | import ghidra.app.decompiler.DecompInterface;
11 | import ghidra.app.decompiler.DecompileOptions;
12 | import ghidra.app.decompiler.DecompileResults;
13 | import ghidra.app.script.GhidraScript;
14 | import ghidra.framework.model.DomainFile;
15 | import ghidra.framework.model.DomainObject;
16 | import ghidra.program.model.data.DataTypeWriter;
17 | import ghidra.program.model.listing.Function;
18 | import ghidra.program.model.listing.Program;
19 | import ghidra.program.model.mem.Memory;
20 | import ghidra.program.model.symbol.Namespace;
21 | import ghidra.program.model.symbol.Symbol;
22 | import ghidra.program.model.symbol.SymbolIterator;
23 | import ghidra.program.model.symbol.SymbolTable;
24 | import ghidra.program.model.symbol.SymbolType;
25 | import java.io.File;
26 | import java.io.FileWriter;
27 | import java.io.IOException;
28 | import java.io.PrintWriter;
29 |
30 | public class ExportDecomp extends GhidraScript
31 | {
32 | @Override protected void run() throws Exception
33 | {
34 | File outDir = askDirectory("Output Folder", "");
35 | int outVer = askInt("File Version to Export", "");
36 |
37 | // We get the DomainFile this way to ensure we get a GhidraFile and not
38 | // a DomainProxyFile. This is because DomainProxyFile does not handle
39 | // getting anything but the latest version of a file.
40 | DomainFile f = parseDomainFile(currentProgram.getDomainFile().getPathname());
41 |
42 | DomainObject obj = f.getReadOnlyDomainObject(this, outVer, monitor);
43 |
44 | Program p = (Program)obj;
45 |
46 | DecompInterface decomp = new DecompInterface();
47 | DecompileOptions options = new DecompileOptions();
48 | decomp.setOptions(options);
49 | decomp.toggleCCode(true);
50 | decomp.toggleSyntaxTree(true);
51 | decomp.setSimplificationStyle("decompile");
52 |
53 | decomp.openProgram(p);
54 |
55 | SymbolTable st = p.getSymbolTable();
56 | SymbolIterator si = st.getSymbolIterator();
57 |
58 | while (si.hasNext())
59 | {
60 | Symbol s = si.next();
61 | if (s.getSymbolType() == SymbolType.FUNCTION && !s.isExternal() && s.getAddress().getOffset() < 0x0043d160)
62 | {
63 | Function fun = p.getFunctionManager().getFunctionAt(s.getAddress());
64 | if (!fun.isThunk())
65 | {
66 | extractFunction(decomp, fun, outDir);
67 | }
68 | }
69 | }
70 |
71 | File datatypesFile = new File(outDir, "types.h");
72 | FileWriter fw = new FileWriter(datatypesFile);
73 |
74 | DataTypeWriter dtw = new DataTypeWriter(p.getDataTypeManager(), fw);
75 | dtw.write(p.getDataTypeManager(), monitor);
76 | fw.close();
77 |
78 | obj.release(this);
79 | }
80 |
81 | String getFullName(Namespace n)
82 | {
83 | String s = n.getName();
84 | while (!n.getParentNamespace().isGlobal())
85 | {
86 | n = n.getParentNamespace();
87 | s = n.getName() + "::" + s;
88 | }
89 | return s;
90 | }
91 |
92 | void extractFunction(DecompInterface decomp, Function f, File outputDir) throws IOException
93 | {
94 | DecompileResults res = decomp.decompileFunction(f, 120, monitor);
95 | if (res.getDecompiledFunction() != null)
96 | {
97 | outputDir = new File(outputDir, f.getParentNamespace().getName());
98 | outputDir.mkdirs();
99 | File outputFile = new File(outputDir, f.getName() + ".c");
100 | FileWriter fw = new FileWriter(outputFile);
101 | PrintWriter pw = new PrintWriter(fw);
102 | pw.write(res.getDecompiledFunction().getC());
103 | pw.close();
104 | }
105 | else
106 | {
107 | printf("Can't decompile %s\n", getFullName(f));
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/scripts/ghidra/ExportDelinker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * LICENSE
3 | */
4 | // Description
5 | //@author renzo904
6 | //@category exports
7 | //@keybinding
8 | //@menupath Skeleton
9 | //@toolbar Skeleton
10 | import static java.util.Map.entry;
11 |
12 | import ghidra.app.analyzers.RelocationTableSynthesizerAnalyzer;
13 | import ghidra.app.script.GhidraScript;
14 | import ghidra.app.services.Analyzer;
15 | import ghidra.app.util.DomainObjectService;
16 | import ghidra.app.util.Option;
17 | import ghidra.app.util.exporter.CoffRelocatableObjectExporter;
18 | import ghidra.app.util.importer.MessageLog;
19 | import ghidra.framework.model.DomainFile;
20 | import ghidra.framework.model.DomainObject;
21 | import ghidra.program.model.address.AddressSet;
22 | import ghidra.program.model.listing.GhidraClass;
23 | import ghidra.program.model.mem.Memory;
24 | import ghidra.program.model.symbol.Namespace;
25 | import ghidra.program.model.symbol.Symbol;
26 | import java.io.File;
27 | import java.nio.charset.StandardCharsets;
28 | import java.nio.file.Files;
29 | import java.util.ArrayList;
30 | import java.util.Arrays;
31 | import java.util.Iterator;
32 | import java.util.List;
33 | import java.util.Map;
34 |
35 | public class ExportDelinker extends GhidraScript
36 | {
37 | @Override protected void run() throws Exception
38 | {
39 | // First run the Relocation Table Synthesizer, to pickup any potentially
40 | // new globals in the reloc table.
41 | Analyzer analyzer = new RelocationTableSynthesizerAnalyzer();
42 | analyzer.added(currentProgram, currentProgram.getMemory(), monitor, new MessageLog());
43 |
44 | // Then, export the COFFs.
45 | CoffRelocatableObjectExporter exporter = new CoffRelocatableObjectExporter();
46 |
47 | List