From 28b62c3b174317ac78f79d9aeb4805d51c6293b0 Mon Sep 17 00:00:00 2001 From: Dethrace Engineering Department <78985374+dethrace-labs@users.noreply.github.com> Date: Fri, 26 Sep 2025 11:34:52 +1200 Subject: [PATCH 1/6] Update README.md --- reccmp/README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/reccmp/README.md b/reccmp/README.md index da01d4d6..49bb7c55 100644 --- a/reccmp/README.md +++ b/reccmp/README.md @@ -6,6 +6,7 @@ To run MSVC 4.20 outside of a non-Windows environment, you can use a Docker imag We are targetting being accurate to CARM95.EXE. - Created date: "16 October 1997" - SHA256 hash: `c6040203856b71e6a22d2a29053a1eadd1a2ab41bce97b6031d745079bc07bdf` +- [Download from archive.org](https://archive.org/download/carm-95/CARM95.EXE) ## Build container image ```sh @@ -23,10 +24,21 @@ When running this container, you must define: | `/build` | `/code/dethrace/build-msvc420` | Volume mount. This directory must exist but can start off empty. Note that this build directory _cannot be_ the same as your "regular" build directory. | | `/original` | `/games/carma` | Volume mount. Path to a directory with a copy of the original CARM95.EXE file | -### Generating an HTML diff - +### Diffing a single function This is the primary flow for making a change to the code and viewing the comparison to the original executable. +```sh +docker run --platform linux/amd64 \ + -e CMAKE_FLAGS="-G Ninja -DCMAKE_BUILD_TYPE=Debug -DMSVC_42_FOR_RECCMP=on" \ + -v :/source \ + -v :/build \ + -v :/original:ro \ + msvc420-wine -- \ + reccmp-reccmp --target CARM95 --verbose FUNCTION_ADDRESS +``` + +### Generating an HTML diff + ```sh docker run --platform linux/amd64 \ -e CMAKE_FLAGS="-G Ninja -DCMAKE_BUILD_TYPE=Debug -DMSVC_42_FOR_RECCMP=on" \ @@ -39,8 +51,42 @@ docker run --platform linux/amd64 \ After running, a `report.html` file will be created in the build-msvc420 directory. +### Wrapping it up + +I wrapped this logic up into a little `zsh` function, which makes it easy to run from command line. +- `dr-reccmp` to generate the overall html report +- `dr-reccmp 0x123456` to diff a single function. + +```zsh +function dr-reccmp { + local arg=() + + if [[ -n "$1" ]]; then + arg+=(--verbose "$1") + else + # only download latest report if changed + curl -fsSL --etag-save reccmp-report-etag-new.txt --etag-compare reccmp-report-etag.txt -o reccmp-report-main.json https://raw.githubusercontent.com/dethrace-labs/reccmp-report/main/report.json + if [ -s reccmp-report-etag-new.txt ]; then + mv -f reccmp-report-etag-new.txt reccmp-report-etag.txt + fi + arg=() + arg+=(--html /source/reccmp-report.html) + arg+=(--diff /source/reccmp-report-main.json) + arg+=(--svg /source/reccmp.svg) + fi + + docker run --platform linux/amd64 \ + -e CMAKE_FLAGS="-G Ninja -DCMAKE_BUILD_TYPE=Debug -DMSVC_42_FOR_RECCMP=on" \ + -v .:/source \ + -v ./build_msvc42:/build \ + -v /opt/carma/:/original:ro \ + msvc420-wine -- \ + reccmp-reccmp --target CARM95 "${arg[@]}" +} +``` + ### Make a pull request change -reccmp will run against the code in the PR branch. If any functions decrease in accuracy the PR validation will fail. +reccmp will run against the code in the PR branch. If any functions decrease in accuracy the PR validation will emit a warning. If the functions which decreased were not changed in the PR, the PR can still be accepted, as the decrease will generally be due to compiler entropy. -When the PR is merged, the updated report is stored in https://github.com/dethrace-labs/reccmp-report, and this is used to compare the next PR +When the PR is merged into `main`, the updated report is stored in https://github.com/dethrace-labs/reccmp-report, and this is used to compare the next PR From 93c3699f2c790119e9d19ae9dc44127cbf43584f Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Fri, 26 Sep 2025 18:15:25 +0200 Subject: [PATCH 2/6] Fix compile errors with modern gcc (#480) --- src/DETHRACE/common/car.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/DETHRACE/common/car.c b/src/DETHRACE/common/car.c index 757c7a95..0655b461 100644 --- a/src/DETHRACE/common/car.c +++ b/src/DETHRACE/common/car.c @@ -472,6 +472,10 @@ void InitialiseCar2(tCar_spec* pCar, int pClear_disabled_flag) { case eDriver_local_human: pCar->car_ID = 0; break; +#ifdef DETHRACE_FIX_BUGS + default: + break; +#endif } PossibleService(); pCar->box_face_ref = gFace_num__car - 2; @@ -1327,7 +1331,7 @@ void ApplyPhysicsToCars(tU32 last_frame_time, tU32 pTime_difference) { } for (i = 0; i < gNum_active_cars; i++) { car = gActive_car_list[i]; - car_info = car; + car_info = (tCollision_info*)car; car->dt = -1.f; if (car->message.type == NETMSGID_MECHANICS && car->message.time >= gLast_mechanics_time && car->message.time <= gLast_mechanics_time + time_step) { // time between car message and next mechanics @@ -1370,11 +1374,11 @@ void ApplyPhysicsToCars(tU32 last_frame_time, tU32 pTime_difference) { if (non_car->collision_info.doing_nothing_flag) { continue; } - car_info = non_car; + car_info = (tCollision_info*)non_car; car_info->dt = -1.f; if (car_info->message.type == NETMSGID_NONCAR_INFO && car_info->message.time >= gLast_mechanics_time && gLast_mechanics_time + time_step >= car_info->message.time) { car_info->dt = (gLast_mechanics_time + time_step - car_info->message.time) / 1000.0f; - GetNetPos(car_info); + GetNetPos((tCar_spec*)car_info); } if (car_info->box_face_ref != gFace_num__car && (car_info->box_face_ref != gFace_num__car - 1 From 26a5d1813f8ed8bb844760419aa675ad810b8b40 Mon Sep 17 00:00:00 2001 From: BSzili Date: Sun, 28 Sep 2025 07:54:52 +0200 Subject: [PATCH 3/6] Replace leftover magic numbers with NETMSGID_* enums --- src/DETHRACE/common/crush.c | 2 +- src/DETHRACE/common/oil.c | 2 +- src/DETHRACE/common/powerup.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DETHRACE/common/crush.c b/src/DETHRACE/common/crush.c index c4ddd33f..ec5a83f8 100644 --- a/src/DETHRACE/common/crush.c +++ b/src/DETHRACE/common/crush.c @@ -1177,7 +1177,7 @@ int DoCrashEarnings(tCar_spec* pCar1, tCar_spec* pCar2) { credits_squared = sqr(0.7f / victim->car_model_actors[victim->principal_car_actor].crush_data.softness_factor) * gWasted_creds[gProgram_state.skill_level] + 50.0f; credits = 100 * (int)(credits_squared / 100.0f); if (gNet_mode) { - message = NetBuildMessage(0x18u, 0); + message = NetBuildMessage(NETMSGID_WASTED, 0); message->contents.data.wasted.victim = NetPlayerFromCar(victim)->ID; if (NetPlayerFromCar(culprit)) { message->contents.data.wasted.culprit = NetPlayerFromCar(culprit)->ID; diff --git a/src/DETHRACE/common/oil.c b/src/DETHRACE/common/oil.c index f478256c..33337639 100644 --- a/src/DETHRACE/common/oil.c +++ b/src/DETHRACE/common/oil.c @@ -324,7 +324,7 @@ void ProcessOilSpills(tU32 pFrame_period) { gOily_spills[i].stop_time = 0; SetInitialOilStuff(&gOily_spills[i], the_model); if (gNet_mode != eNet_mode_none) { - message = NetBuildMessage(30, 0); + message = NetBuildMessage(NETMSGID_OILSPILL, 0); message->contents.data.oil_spill.player = NetPlayerFromCar(gOily_spills[i].car)->ID; message->contents.data.oil_spill.full_size = gOily_spills[i].full_size; message->contents.data.oil_spill.grow_rate = gOily_spills[i].grow_rate; diff --git a/src/DETHRACE/common/powerup.c b/src/DETHRACE/common/powerup.c index eff4a333..b32309de 100644 --- a/src/DETHRACE/common/powerup.c +++ b/src/DETHRACE/common/powerup.c @@ -179,7 +179,7 @@ void LosePowerupX(tPowerup* pThe_powerup, int pTell_net_players) { pThe_powerup->lose_proc(pThe_powerup, pThe_powerup->car); } if (gNet_mode != eNet_mode_none) { - the_message = NetBuildMessage(21, 0); + the_message = NetBuildMessage(NETMSGID_POWERUP, 0); the_message->contents.data.powerup.event = ePowerup_lost; the_message->contents.data.powerup.player = gLocal_net_ID; the_message->contents.data.powerup.event = GET_POWERUP_INDEX(pThe_powerup); @@ -287,7 +287,7 @@ int GotPowerupX(tCar_spec* pCar, int pIndex, int pTell_net_players, int pDisplay PratcamEvent(the_powerup->prat_cam_event); } if (gNet_mode != eNet_mode_none && pTell_net_players && pIndex == original_index && !ps_power) { - the_message = NetBuildMessage(21, 0); + the_message = NetBuildMessage(NETMSGID_POWERUP, 0); the_message->contents.data.powerup.event = ePowerup_gained; the_message->contents.data.powerup.player = gLocal_net_ID; the_message->contents.data.powerup.powerup_index = pIndex; From 67a3da8eca7ae7d9aa420b642f4953e894492441 Mon Sep 17 00:00:00 2001 From: Carlo Bramini Date: Fri, 26 Sep 2025 15:04:10 +0200 Subject: [PATCH 4/6] WIN32: fix undefined reference to GetAdaptersAddresses on MinGW This PR fixes issue #487. --- src/harness/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/harness/CMakeLists.txt b/src/harness/CMakeLists.txt index 7563a2dc..52819007 100644 --- a/src/harness/CMakeLists.txt +++ b/src/harness/CMakeLists.txt @@ -69,7 +69,7 @@ if(MSVC_42_FOR_RECCMP) target_sources(harness PRIVATE os/null.c) elseif(WIN32) target_sources(harness PRIVATE os/windows.c) - target_link_libraries(harness PRIVATE dbghelp ws2_32) + target_link_libraries(harness PRIVATE dbghelp ws2_32 iphlpapi) elseif(APPLE) target_sources(harness PRIVATE os/macos.c) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") From 426abacacc56b9e9e3182492163890976c3da68f Mon Sep 17 00:00:00 2001 From: Dethrace Labs <78985374+dethrace-labs@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:13:54 +1300 Subject: [PATCH 5/6] fixes incorrect EdgeU assembly match --- src/DETHRACE/common/depth.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DETHRACE/common/depth.c b/src/DETHRACE/common/depth.c index 9a785c0f..357d0d0a 100644 --- a/src/DETHRACE/common/depth.c +++ b/src/DETHRACE/common/depth.c @@ -260,9 +260,9 @@ br_scalar EdgeU(br_angle pSky, br_angle pView, br_angle pPerfect) { br_scalar b; br_scalar c; - a = cos(BrAngleToRadian(pPerfect)) * cos(BrAngleToRadian(pPerfect)); - b = sin(BrAngleToRadian(pView)); - c = cos(BrAngleToRadian(pView) + 1.0f) * BrAngleToRadian(pSky); + a = BR_COS(pPerfect) * BR_COS(pPerfect); + b = BR_SIN(pView); + c = (BR_COS(pView) + 1.0f) * BrAngleToRadian(pSky); return b * a / c; } From b6feda3b43039d79aa09daa58667b5389f606945 Mon Sep 17 00:00:00 2001 From: Dethrace Labs <78985374+dethrace-labs@users.noreply.github.com> Date: Wed, 1 Oct 2025 12:00:47 +1300 Subject: [PATCH 6/6] fixes cd audio not stopping during cutscenes --- src/DETHRACE/common/sound.c | 16 ++++++++-------- src/S3/s3.c | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/DETHRACE/common/sound.c b/src/DETHRACE/common/sound.c index 1a115e18..692582f2 100644 --- a/src/DETHRACE/common/sound.c +++ b/src/DETHRACE/common/sound.c @@ -37,7 +37,7 @@ int gRandom_Rockin_MIDI_tunes[3] = { 9500, 9501, 9502 }; // GLOBAL: CARM95 0x00514958 int gRandom_CDA_tunes[8] = { 9600, 9601, 9602, 9603, 9604, 9605, 9606, 9607 }; /* dethrace: Changed to size 8 */ -// GLOBAL: CARM95 0x00514978 +// GLOBAL: CARM95 0x0051498c int gCDA_is_playing; // GLOBAL: CARM95 0x0051497c @@ -52,7 +52,7 @@ int gSound_sources_inited; // GLOBAL: CARM95 0x00514988 int gMusic_available; -// GLOBAL: CARM95 0x0051498c +// GLOBAL: CARM95 0x00514978 tS3_sound_tag gCDA_tag; // GLOBAL: CARM95 0x00514990 @@ -750,13 +750,13 @@ int DRS3StartCDA(tS3_sound_id pCDA_id) { } while (pCDA_id == gLast_tune); } gLast_tune = pCDA_id; - gCDA_is_playing = DRS3StartSoundNoPiping(gMusic_outlet, pCDA_id); + gCDA_tag = DRS3StartSoundNoPiping(gMusic_outlet, pCDA_id); #if defined(DETHRACE_FIX_BUGS) // Initial CD music volume was not set correctly DRS3SetOutletVolume(gMusic_outlet, 42 * gProgram_state.music_volume); #endif - gCDA_tag = gCDA_is_playing; - if (!gCDA_is_playing) { + gCDA_is_playing = gCDA_tag != 0; + if (gCDA_tag == 0) { gCD_is_disabled = 1; S3DisableCDA(); } @@ -765,7 +765,7 @@ int DRS3StartCDA(tS3_sound_id pCDA_id) { } } } - return gCDA_tag; + return gCDA_is_playing; } // IDA: int __cdecl DRS3StopCDA() @@ -777,14 +777,14 @@ int DRS3StopCDA(void) { gCDA_is_playing = 0; gCDA_tag = 0; } - return gCDA_tag; + return gCDA_is_playing; } // IDA: void __cdecl StartMusic() // FUNCTION: CARM95 0x00465899 void StartMusic(void) { if (gCD_fully_installed) { - gCDA_tag = DRS3StartCDA(9999); + gCDA_is_playing = DRS3StartCDA(9999); } } diff --git a/src/S3/s3.c b/src/S3/s3.c index 324b3a4d..f2668364 100644 --- a/src/S3/s3.c +++ b/src/S3/s3.c @@ -1254,7 +1254,8 @@ tS3_channel* S3GetChannelForTag(tS3_sound_tag tag) { if (!tag) { return 0; } - for (o = gS3_outlets; o && o->id != tag; o = o->next) { + // the first char of tag is the outlet id. See `S3GenerateTag` + for (o = gS3_outlets; o && o->id != (tag & 0xff); o = o->next) { ; } if (!o) {