diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd9289fc..ffb88d4c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,3 +36,9 @@ jobs: run: python scripts/create_devenv.py scripts/dls scripts/prefix - name: Build binary run: scripts/build.bat + - uses: actions/upload-artifact@v3 + with: + name: th06e + path: | + build/th06e.exe + build/th06e.pdb diff --git a/.github/workflows/compare.yml b/.github/workflows/compare.yml new file mode 100644 index 00000000..98037f66 --- /dev/null +++ b/.github/workflows/compare.yml @@ -0,0 +1,59 @@ +name: Compare reimplementation + +on: + workflow_run: + workflows: [Build binary] + types: [completed] + workflow_dispatch: + inputs: + workflow_id: + required: true + type: number + +jobs: + compare: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: Install satsuki + uses: robinraju/release-downloader@v1.8 + with: + repository: happyhavoc/satsuki + latest: false + tag: v0.1.0 + fileName: satsuki.exe + - name: Add satsuki to PATH + run: | + echo $pwd.Path >> $env:GITHUB_PATH + - name: Download original game binary for comparison + run: curl -L $env:ORIGINAL_URL -o resources/game.exe + env: + ORIGINAL_URL: ${{ secrets.ORIGINAL_URL }} + - name: Download reimplemented game binary for comparison (1/2) + uses: actions/github-script@v6 + with: + script: | + let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run ? context.payload.workflow_run.id : context.payload.inputs.workflow_id, + }); + let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { + return artifact.name == "th06e" + })[0]; + let download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + let fs = require('fs'); + fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/th06e.zip`, Buffer.from(download.data)); + - name: Download reimplemented game binary for comparison (2/2) + run: | + mkdir -p build + unzip th06e.zip -d build + - name: Use implemented.csv from target repo + run: curl -L https://raw.githubusercontent.com/${{github.event.workflow_run.head_repository.full_name}}/${{github.event.workflow_run.head_commit.id}}/config/implemented.csv -o config/implemented.csv + - name: Create diff summary + run: python scripts/diff_all_functions.py > $env:GITHUB_STEP_SUMMARY diff --git a/config/implemented.csv b/config/implemented.csv new file mode 100644 index 00000000..f2170fbc --- /dev/null +++ b/config/implemented.csv @@ -0,0 +1,25 @@ +ChainElem::ChainElem +ChainElem::~ChainElem +ChainElem::Allocate +Chain::ReleaseSingleChain +Chain::Release +Chain::Cut +Chain::RunDrawChain +Chain::RunCalcChain +Chain::AddToCalcChain +Chain::AddToDrawChain +InitD3dInterface +GetJoystickCaps +SetButtonFromControllerInputs +SetButtonFromDirectInputJoystate +GetControllerInput +GetInput +GameErrorContextFatal +GameErrorContextLog +GameErrorContext::Flush +ResetKeyboard +WindowProc +CreateGameWindow +WinMain +CheckForRunningGameInstance +DebugPrint2 diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/scripts/diff_all_functions.py b/scripts/diff_all_functions.py new file mode 100755 index 00000000..ed021ae9 --- /dev/null +++ b/scripts/diff_all_functions.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +import csv +import difflib +from pathlib import Path + +from generate_function_diff import generate_function_diff + +def main(): + base_dir = Path(__file__).parent.parent + config_dir = base_dir / 'config' + + implemented_csv = config_dir / 'implemented.csv' + + with open(implemented_csv) as f: + vals = [] + for row in csv.reader(f): + orig, reimpl = generate_function_diff(row[0]) + vals.append({ + 'name': row[0], + 'diff': "\n".join(difflib.unified_diff(orig.split('\n'), reimpl.split('\n'), 'Original', 'Reimplementation', lineterm='')), + 'ratio': difflib.SequenceMatcher(None, orig, reimpl).ratio(), + }) + + + print("# Report") + print("") + print(f"name | result") + print(f"-----|-------") + for val in vals: + name = val['name'] + id = val['name'].lower().replace(":", "__") + if val['ratio'] != 1: + name = f'[{name}](#user-content-{id})' + print(f"{name} | {val['ratio'] * 100:.2f}%") + + + for val in vals: + if val['ratio'] != 1: + id = val['name'].lower().replace(":", "__") + print("") + print("") + print(f'

{val["name"]}

') + print("") + print("```diff") + print(val['diff']) + print("```") + print("") + print("
") + + +if __name__ == '__main__': + main() diff --git a/scripts/generate_function_diff b/scripts/generate_function_diff deleted file mode 100755 index f1cdd2c2..00000000 --- a/scripts/generate_function_diff +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -set -e - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -BASE_DIR=$SCRIPT_DIR/.. -BUILD_DIR=$BASE_DIR/build -RESOURCES_DIR=$BASE_DIR/resources -CONFIG_DIR=$BASE_DIR/config -DIFF_DIR=$BASE_DIR/diff - -FUNCTION_NAME=$1 -FUNCTION_OUTPUT_DIR=$DIFF_DIR/$FUNCTION_NAME - -mkdir -p $FUNCTION_OUTPUT_DIR -satsuki --mapping-file $CONFIG_DIR/mapping.toml disassemble --force-address-zero $RESOURCES_DIR/game.exe $FUNCTION_NAME > $FUNCTION_OUTPUT_DIR/orig.asm - -rm -rf $BUILD_DIR - -pushd $BASE_DIR -./scripts/wineth06 scripts/build.bat -popd - -satsuki --mapping-file $CONFIG_DIR/mapping.toml disassemble --force-address-zero $BUILD_DIR/th06e.exe --pdb-file $BUILD_DIR/th06e.pdb $FUNCTION_NAME > $FUNCTION_OUTPUT_DIR/reimpl.asm diff --git a/scripts/generate_function_diff.py b/scripts/generate_function_diff.py new file mode 100755 index 00000000..4c475673 --- /dev/null +++ b/scripts/generate_function_diff.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +from pathlib import Path +import sys +import os +import subprocess + +def generate_function_diff(fn_name): + base_dir = Path(__file__).parent.parent + build_dir = base_dir / 'build' + config_dir = base_dir / 'config' + diff_dir = base_dir / 'diff' + resource_dir = base_dir / 'resources' + + fs_fn_name = fn_name.replace(':', '__') + + orig_asm_path = diff_dir / fs_fn_name / 'orig.asm' + reimpl_asm_path = diff_dir / fs_fn_name / 'reimpl.asm' + + os.makedirs(diff_dir / fs_fn_name, exist_ok=True) + with open(orig_asm_path, 'w') as out: + out = subprocess.run(['satsuki', '--mapping-file', str(config_dir / 'mapping.toml'), 'disassemble', str(resource_dir / 'game.exe'), '--resolve-names', fn_name], stdout=out) + with open(reimpl_asm_path, 'w') as out: + out = subprocess.run(['satsuki', '--mapping-file', str(config_dir / 'mapping.toml'), 'disassemble', str(build_dir / 'th06e.exe'), '--pdb-file', str(build_dir / 'th06e.pdb'), '--resolve-names', fn_name], stdout=out) + + return orig_asm_path.read_text(), reimpl_asm_path.read_text() + + +def main(): + fn_name = sys.argv[1] + generate_function_diff(fn_name) + + +if __name__ == '__main__': + main()