Merge branch 'main' of https://github.com/dethrace-labs/dethrace into opponent_matching

This commit is contained in:
Dethrace Labs 2025-10-10 20:05:12 +13:00
commit 6af5c19f1b
23 changed files with 959 additions and 89 deletions

View File

@ -51,7 +51,8 @@ jobs:
cmake-generator: Ninja
cmake-toolchain-file: ${{ matrix.platform.cmake-toolchain-file }}
discriminator: ${{ matrix.platform.arch }}
version: 2-latest
version: 3-latest
version-sdl2-compat: 2-head
version-sdl12-compat: 1-head
- name: 'Prepare sources for release'
if: ${{ startsWith(github.ref, 'refs/tags/') }}
@ -71,6 +72,7 @@ jobs:
-DCMAKE_TOOLCHAIN_FILE=${{ matrix.platform.cmake-toolchain-file }} \
-DDETHRACE_PLATFORM_SDL1=ON \
-DDETHRACE_PLATFORM_SDL2=ON \
-DDETHRACE_PLATFORM_SDL3=ON \
-DDETHRACE_PLATFORM_SDL_DYNAMIC=ON \
${{ matrix.platform.cmake-args }}
- name: 'Build (CMake)'

View File

@ -44,6 +44,7 @@ option(DETHRACE_SOUND_ENABLED "Include audio support" ON)
option(DETHRACE_NET_ENABLED "Include net support" ON)
option(DETHRACE_PLATFORM_SDL1 "Support SDL 1.2 platform driver" OFF)
option(DETHRACE_PLATFORM_SDL2 "Support SDL 2 platform driver" ON)
option(DETHRACE_PLATFORM_SDL3 "Support SDL 3 platform driver" OFF)
option(MSVC_42_FOR_RECCMP "Build with MSVC 4.2 to match assembly" OFF)
@ -54,6 +55,7 @@ if(MSVC_42_FOR_RECCMP)
set(DETHRACE_NET_ENABLED OFF)
set(DETHRACE_PLATFORM_SDL1 OFF)
set(DETHRACE_PLATFORM_SDL2 OFF)
set(DETHRACE_PLATFORM_SDL3 OFF)
set(BRENDER_BUILD_DRIVERS OFF)
set(CMAKE_C_FLAGS_DEBUG "/Od /Oi /Zi /MLd")
@ -107,6 +109,12 @@ if(DETHRACE_PLATFORM_SDL2)
math(EXPR count_sdl_platforms "${count_sdl_platforms} + 1")
endif()
if(DETHRACE_PLATFORM_SDL3)
find_package(SDL3 CONFIG REQUIRED)
list(APPEND DETHRACE_PLATFORMS SDL3)
math(EXPR count_sdl_platforms "${count_sdl_platforms} + 1")
endif()
if(count_sdl_platforms GREATER 1)
# Force dynamic SDL when enabling 2 (or more) SDL platform backends
set(dynamic_sdl_force TRUE)

View File

@ -298,6 +298,12 @@ if(DETHRACE_INSTALL)
OPTIONAL
)
endif()
if(DETHRACE_PLATFORM_SDL3)
install(FILES "$<TARGET_FILE:SDL3::SDL3>"
DESTINATION "."
OPTIONAL
)
endif()
endif()
endif()
endif()

View File

@ -140,8 +140,25 @@ int gAllow_open_to_fail = 1;
// GLOBAL: CARM95 0x0050a5c8
int gDecode_thing = '@';
#define DECODE_STRING_SECRET 50
// GLOBAL: CARM95 0x0050a5d0
char gDecode_string[] = { 0x9B, 0x52, 0x93, 0x9F, 0x52, 0x98, 0x9B, 0x96, 0x96, 0x9E, 0x9B, 0xA0, 0x99, 0x0 };
char gDecode_string[] = {
'i' + DECODE_STRING_SECRET,
' ' + DECODE_STRING_SECRET,
'a' + DECODE_STRING_SECRET,
'm' + DECODE_STRING_SECRET,
' ' + DECODE_STRING_SECRET,
'f' + DECODE_STRING_SECRET,
'i' + DECODE_STRING_SECRET,
'd' + DECODE_STRING_SECRET,
'd' + DECODE_STRING_SECRET,
'l' + DECODE_STRING_SECRET,
'i' + DECODE_STRING_SECRET,
'n' + DECODE_STRING_SECRET,
'g' + DECODE_STRING_SECRET,
'\0'
};
// GLOBAL: CARM95 0x00531f00
int gFunk_groove_flags[30];
@ -400,7 +417,7 @@ void LoadGeneralParameters(void) {
fgets(s, sizeof(s) - 1, f);
fclose(f);
for (i = 0; i < strlen(gDecode_string); i++) {
gDecode_string[i] -= 50;
gDecode_string[i] -= DECODE_STRING_SECRET;
}
// trim trailing CRLF etc
@ -413,7 +430,7 @@ void LoadGeneralParameters(void) {
}
for (i = 0; i < strlen(gDecode_string); i++) {
gDecode_string[i] += 50;
gDecode_string[i] += DECODE_STRING_SECRET;
}
}
PathCat(the_path, gApplication_path, "GENERAL.TXT");

View File

@ -1595,7 +1595,7 @@ void SelectRaceDraw(int pCurrent_choice, int pCurrent_mode) {
fputs("*************", f);
}
}
gDecode_thing ^= 0x40u;
gDecode_thing ^= '@';
fclose(f);
EncodeAllFilesInDirectory("");
EncodeAllFilesInDirectory("CARS");

View File

@ -98,6 +98,14 @@ void EncodeLine(char* pS) {
FILE* test;
unsigned char c;
#ifdef DETHRACE_FIX_BUGS
// Demo has its own decryption key + behavior
if (harness_game_info.mode == eGame_carmageddon_demo) {
EncodeLine_DEMO(pS);
return;
}
#endif
len = strlen(pS);
key = (char*)gLong_key;
if (gEncryption_method == 0) {
@ -1441,6 +1449,14 @@ void DecodeLine2(char* pS) {
unsigned char c;
char* key;
#ifdef DETHRACE_FIX_BUGS
// Demo has its own decryption key + behavior
if (harness_game_info.mode == eGame_carmageddon_demo) {
DecodeLine2_DEMO(pS);
return;
}
#endif
len = strlen(pS);
key = (char*)gLong_key;
#ifdef DETHRACE_FIX_BUGS
@ -1494,6 +1510,14 @@ void EncodeLine2(char* pS) {
unsigned char c;
char* key;
#ifdef DETHRACE_FIX_BUGS
// Demo has its own decryption key + behavior
if (harness_game_info.mode == eGame_carmageddon_demo) {
EncodeLine2_DEMO(pS);
return;
}
#endif
len = strlen(pS);
count = 0;
key = (char*)gLong_key;
@ -1913,3 +1937,48 @@ void EncodeLine_DEMO(char* pS) {
pS[i] = c;
}
}
void EncodeLine2_DEMO(char* pS) {
int len;
int seed;
int i;
const char* key;
unsigned char c;
#if BR_ENDIAN_BIG
const tU32 gLong_key_DEMO[] = { 0x58503A76, 0xCBB68565, 0x15CD5B07, 0xB168DE3A };
#else
const tU32 gLong_key_DEMO[] = { 0x763A5058, 0x6585B6CB, 0x75BCD15, 0x3ADE68B1 };
#endif
len = strlen(pS);
key = (char*)gLong_key_DEMO;
while (len != 0 && (pS[len - 1] == '\r' || pS[len - 1] == '\n')) {
pS[len - 1] = 0;
len--;
}
seed = len % 16;
for (i = 0; i < len; i++) {
c = pS[i];
if (c == '\t') {
c = 0x9F;
}
c -= 32;
c ^= key[seed];
c &= 0x7f;
c += 32;
if (c == 0x9F) {
c = '\t';
}
if (c == '\n' || c == '\r') {
c |= 0x80;
}
seed = (seed + 7) % 16;
pS[i] = c;
}
}
void DecodeLine2_DEMO(char* pS) {
EncodeLine_DEMO(pS);
}

View File

@ -186,4 +186,10 @@ void BlendifyMaterialPrimitively(br_material* pMaterial, int pPercent);
void BlendifyMaterial(br_material* pMaterial, int pPercent);
void EncodeLine_DEMO(char* pS);
void EncodeLine2_DEMO(char* pS);
void DecodeLine2_DEMO(char* pS);
#endif

View File

@ -175,7 +175,7 @@ int ReceiveHostResponses(void) {
int already_registered;
char addr_string[32];
unsigned int sa_len;
socklen_t sa_len;
int error;
sa_len = sizeof(gRemote_addr);
@ -519,7 +519,7 @@ tNet_message* PDNetGetNextMessage(tNet_game_details* pDetails, void** pSender_ad
int msg_type;
char addr_str[32];
unsigned int sa_len;
socklen_t sa_len;
int res;
tNet_message* msg;

View File

@ -843,6 +843,11 @@ int original_main(int pArgc, char** pArgv) {
for (i = 1; i < pArgc; i++) {
if (strcasecmp(pArgv[i], "-hires") == 0) {
#ifdef DETHRACE_FIX_BUGS
if (!PDCheckDriveExists("DATA/64X48X8/HEADUP.TXT")) {
PDFatalError("No high resolution data (\"DATA/64X48X8\") is available. Run game without -hires.");
}
#endif
gGraf_spec_index = 1;
} else if (strcasecmp(pArgv[i], "-yon") == 0 && i < pArgc - 1) {
i++;

View File

@ -99,7 +99,7 @@ endif()
if(DETHRACE_PLATFORM_SDL2)
target_sources(harness PRIVATE
platforms/sdl2.c
platforms/sdl_scancode_map.h
platforms/sdl2_scancode_map.h
platforms/sdl2_syms.h
)
target_compile_definitions(harness PRIVATE DETHRACE_PLATFORM_SDL2)
@ -112,6 +112,21 @@ if(DETHRACE_PLATFORM_SDL2)
endif()
endif()
if(DETHRACE_PLATFORM_SDL3)
target_sources(harness PRIVATE
platforms/sdl3.c
platforms/sdl2_scancode_map.h
platforms/sdl3_syms.h
)
target_compile_definitions(harness PRIVATE DETHRACE_PLATFORM_SDL3)
if(DETHRACE_PLATFORM_SDL_DYNAMIC)
set_property(SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/platforms/sdl3.c" APPEND PROPERTY INCLUDE_DIRECTORIES "$<TARGET_PROPERTY:SDL3::SDL3,INTERFACE_INCLUDE_DIRECTORIES>")
set_property(GLOBAL APPEND PROPERTY DETHRACE_BUILD_RPATHS "$<TARGET_FILE_DIR:SDL3::SDL3>")
else()
target_link_libraries(harness PRIVATE SDL3::SDL3)
endif()
endif()
if(DETHRACE_PLATFORM_SDL_DYNAMIC)
target_compile_definitions(harness PRIVATE DETHRACE_SDL_DYNAMIC)
target_link_libraries(harness PRIVATE ${CMAKE_DL_LIBS})

View File

@ -22,16 +22,22 @@ extern void Harness_Platform_Init(tHarness_platform* platform);
extern const tPlatform_bootstrap SDL1_bootstrap;
extern const tPlatform_bootstrap SDL2_bootstrap;
extern const tPlatform_bootstrap SDL3_bootstrap;
static const tPlatform_bootstrap* platform_bootstraps[] = {
#if defined(DETHRACE_PLATFORM_SDL2) && defined(DETHRACE_PLATFORM_SDL1)
#if defined(DETHRACE_PLATFORM_SDL2)
&SDL2_bootstrap,
&SDL1_bootstrap
#elif defined(DETHRACE_PLATFORM_SDL2)
&SDL2_bootstrap
#elif defined(DETHRACE_PLATFORM_SDL1)
&SDL1_bootstrap
#else
#define HAS_PLATFORM_BOOTSTRAP
#endif
#if defined(DETHRACE_PLATFORM_SDL3)
&SDL3_bootstrap,
#define HAS_PLATFORM_BOOTSTRAP
#endif
#if defined(DETHRACE_PLATFORM_SDL1)
&SDL1_bootstrap,
#define HAS_PLATFORM_BOOTSTRAP
#endif
#ifndef HAS_PLATFORM_BOOTSTRAP
// This is the case for MSVC 4.20 builds
NULL
#endif
@ -180,6 +186,9 @@ static void Harness_DetectGameMode(void) {
} else if (strstr(buffer, "NOWA GRA") != NULL) {
harness_game_info.localization = eGameLocalization_polish;
LOG_INFO2("Language: \"%s\"", "Polish");
} else if (strstr(buffer, "NOUVELLE PARTIE") != NULL) {
harness_game_info.localization = eGameLocalization_french;
LOG_INFO2("Language: \"%s\"", "French");
} else {
LOG_INFO("Language: unrecognized");
}
@ -541,6 +550,14 @@ int Harness_Hook_isalnum(int c) {
return 1;
}
}
} if (harness_game_info.localization == eGameLocalization_french) {
// French diacritic letters in Windows-1252
unsigned char letters[] = { 140, 156, 159, 192, 194, 198, 199, 200, 201, 202, 203, 206, 207, 212, 217, 219, 220, 224, 226, 230, 231, 232, 233, 234, 235, 238, 239, 244, 249, 251, 252, 255 };
for (i = 0; i < (int)sizeof(letters); i++) {
if ((unsigned char)c == letters[i]) {
return 1;
}
}
}
return isalnum(c);

View File

@ -16,6 +16,7 @@ typedef enum {
eGameLocalization_none,
eGameLocalization_german,
eGameLocalization_polish,
eGameLocalization_french,
} tHarness_game_localization;
typedef struct tHarness_game_info {

View File

@ -14,7 +14,7 @@ void debug_print_matrix4(const char* fmt, const char* fn, char* name, br_matrix4
#define BLUE
#if 1 // _MSC_VER == 1020
#if _MSC_VER == 1020
#define LOG_TRACE()
#define LOG_TRACE8()
@ -86,6 +86,13 @@ void debug_print_matrix4(const char* fmt, const char* fn, char* name, br_matrix4
stub_printed = 1; \
}
#define LOG_INFO2(a, b) LOG_INFO(a, b)
#define LOG_INFO3(a, b, c) LOG_INFO(a, b, c)
#define LOG_WARN2(a, b) LOG_WARN(a, b)
#define LOG_WARN3(a, b, c) LOG_WARN(a, b, c)
#define LOG_PANIC2(a, b) LOG_PANIC(a, b)
#define LOG_DEBUG2(a, b) LOG_DEBUG(a, b)
#endif
#endif // ifdef

View File

@ -437,7 +437,7 @@ int OS_GetAdapterAddress(char* name, void* pSockaddr_in) {
}
for (IP_ADAPTER_ADDRESSES* aa = adapter_addrs; aa != NULL; aa = aa->Next) {
LOG_DEBUG("name: %s", aa->FriendlyName); // Skip if name is provided and doesn't match FriendlyName
LOG_DEBUG2("name: %s", aa->FriendlyName); // Skip if name is provided and doesn't match FriendlyName
if (wcslen(wideName) > 0 && wcscmp(aa->FriendlyName, wideName) != 0)
continue;

View File

@ -49,12 +49,11 @@ static const char* const possible_locations[] = {
"libSDL-1.2.so",
};
#endif
#endif
#ifdef DETHRACE_SDL_DYNAMIC
static void* sdl1_so;
#endif
#define SDL_NAME "SDL1"
#define OBJECT_NAME sdl1_so
#define SYMBOL_PREFIX SDL1_
#define FOREACH_SDLX_SYM FOREACH_SDL1_SYM

View File

@ -4,8 +4,8 @@
#include "harness/config.h"
#include "harness/hooks.h"
#include "harness/trace.h"
#include "sdl2_scancode_map.h"
#include "sdl2_syms.h"
#include "sdl_scancode_map.h"
SDL_COMPILE_TIME_ASSERT(sdl2_platform_requires_SDL2, SDL_MAJOR_VERSION == 2);
@ -21,7 +21,7 @@ static int render_width, render_height;
static Uint32 last_frame_time;
void (*gKeyHandler_func)(void);
static void (*gKeyHandler_func)(void);
// 32 bytes, 1 bit per key. Matches dos executable behavior
static br_uint_32 key_state[8];
@ -59,12 +59,11 @@ static const char* const possible_locations[] = {
"libSDL2-2.0.so",
};
#endif
#endif
#ifdef DETHRACE_SDL_DYNAMIC
static void* sdl2_so;
#endif
#define SDL_NAME "SDL2"
#define OBJECT_NAME sdl2_so
#define SYMBOL_PREFIX SDL2_
#define FOREACH_SDLX_SYM FOREACH_SDL2_SYM

View File

@ -4,7 +4,9 @@
#include "dethrace_scancodes.h"
#include <SDL.h>
int sdl_scancode_map[SDL_NUM_SCANCODES] = {
SDL_COMPILE_TIME_ASSERT(sdl2_scancode_map_requires_SDL2, SDL_MAJOR_VERSION == 2);
static int sdl_scancode_map[SDL_NUM_SCANCODES] = {
[SDL_SCANCODE_ESCAPE] = SCANCODE_ESCAPE,
[SDL_SCANCODE_1] = SCANCODE_1,
[SDL_SCANCODE_2] = SCANCODE_2,

View File

@ -42,6 +42,4 @@
X(GetPrefPath, char*, (const char* org, const char* app)) \
X(free, void, (void*))
#undef SDL2_SYM
#endif /* sdl2_syms_h */

View File

@ -0,0 +1,426 @@
#include <SDL3/SDL.h>
#include "harness.h"
#include "harness/config.h"
#include "harness/hooks.h"
#include "harness/trace.h"
#include "sdl3_scancode_map.h"
#include "sdl3_syms.h"
SDL_COMPILE_TIME_ASSERT(sdl3_platform_requires_SDL3, SDL_MAJOR_VERSION == 3);
static SDL_Window* window;
static SDL_Renderer* renderer;
static SDL_Texture* screen_texture;
static uint32_t converted_palette[256];
static br_pixelmap* last_screen_src;
static SDL_GLContext gl_context;
static int render_width, render_height;
static Uint32 last_frame_time;
static void (*gKeyHandler_func)(void);
// 32 bytes, 1 bit per key. Matches dos executable behavior
static br_uint_32 key_state[8];
static struct {
int x, y;
float scale_x, scale_y;
} viewport;
// Callbacks back into original game code
extern void QuitGame(void);
extern br_pixelmap* gBack_screen;
#ifdef DETHRACE_SDL_DYNAMIC
#ifdef _WIN32
static const char * const possible_locations[] = {
"SDL3.dll",
};
#elif defined(__APPLE__)
#define SHARED_OBJECT_NAME "libSDL3"
#define SDL3_LIBNAME "libSDL3.dylib"
#define SDL3_FRAMEWORK "SDL3.framework/Versions/A/SDL3"
static const char * const possible_locations[] = {
"@loader_path/" SDL3_LIBNAME, /* MyApp.app/Contents/MacOS/libSDL3_dylib */
"@loader_path/../Frameworks/" SDL3_FRAMEWORK, /* MyApp.app/Contents/Frameworks/SDL3_framework */
"@executable_path/" SDL3_LIBNAME, /* MyApp.app/Contents/MacOS/libSDL3_dylib */
"@executable_path/../Frameworks/" SDL3_FRAMEWORK, /* MyApp.app/Contents/Frameworks/SDL3_framework */
NULL, /* /Users/username/Library/Frameworks/SDL3_framework */
"/Library/Frameworks" SDL3_FRAMEWORK, /* /Library/Frameworks/SDL3_framework */
SDL3_LIBNAME /* oh well, anywhere the system can see the .dylib (/usr/local/lib or whatever) */
};
#else
static const char * const possible_locations[] = {
"libSDL3.so.0",
"libSDL3.so",
};
#endif
static void *sdl3_so;
#endif
#define SDL_NAME "SDL3"
#define OBJECT_NAME sdl3_so
#define SYMBOL_PREFIX SDL3_
#define FOREACH_SDLX_SYM FOREACH_SDL3_SYM
#include "sdl_dyn_common.h"
static void calculate_viewport(int window_width, int window_height) {
int vp_width, vp_height;
float target_aspect_ratio;
float aspect_ratio;
aspect_ratio = (float)window_width / window_height;
target_aspect_ratio = (float)gBack_screen->width / gBack_screen->height;
vp_width = window_width;
vp_height = window_height;
if (aspect_ratio != target_aspect_ratio) {
if (aspect_ratio > target_aspect_ratio) {
vp_width = window_height * target_aspect_ratio + .5f;
} else {
vp_height = window_width / target_aspect_ratio + .5f;
}
}
viewport.x = (window_width - vp_width) / 2;
viewport.y = (window_height - vp_height) / 2;
viewport.scale_x = (float)vp_width / gBack_screen->width;
viewport.scale_y = (float)vp_height / gBack_screen->height;
}
static int SDL3_Harness_SetWindowPos(void* hWnd, int x, int y, int nWidth, int nHeight) {
// SDL_SetWindowPosition(hWnd, x, y);
if (nWidth == 320 && nHeight == 200) {
nWidth = 640;
nHeight = 400;
}
SDL3_SetWindowSize(hWnd, nWidth, nHeight);
return 0;
}
static void SDL3_Harness_DestroyWindow(void) {
// SDL3_GL_DeleteContext(context);
if (window != NULL) {
SDL3_DestroyWindow(window);
}
SDL3_Quit();
window = NULL;
}
// Checks whether the `flag_check` is the only modifier applied.
// e.g. is_only_modifier(event.key.keysym.mod, KMOD_ALT) returns true when only the ALT key was pressed
static int is_only_key_modifier(SDL_Keymod modifier_flags, SDL_Keymod flag_check) {
return (modifier_flags & flag_check) && (modifier_flags & (SDL_KMOD_CTRL | SDL_KMOD_SHIFT | SDL_KMOD_ALT | SDL_KMOD_GUI)) == (modifier_flags & flag_check);
}
static void SDL3_Harness_ProcessWindowMessages(void) {
SDL_Event event;
while (SDL3_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
if (event.key.windowID != SDL3_GetWindowID(window)) {
continue;
}
if (event.key.key == SDLK_RETURN) {
if (event.key.type == SDL_EVENT_KEY_DOWN) {
if ((event.key.mod & (SDL_KMOD_CTRL | SDL_KMOD_SHIFT | SDL_KMOD_ALT | SDL_KMOD_GUI))) {
// Ignore keydown of RETURN when used together with some modifier
return;
}
} else if (event.key.type == SDL_EVENT_KEY_UP) {
if (is_only_key_modifier(event.key.mod, SDL_KMOD_ALT)) {
SDL3_SetWindowFullscreen(window, (SDL3_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) ? 0 : SDL_WINDOW_FULLSCREEN);
}
}
}
// Map incoming SDL scancode to PC scan code as used by game code
int dethrace_scancode = sdl_scancode_map[event.key.scancode];
if (dethrace_scancode == 0) {
LOG_WARN3("unexpected scan code %s (%d)", SDL3_GetScancodeName(event.key.scancode), event.key.scancode);
return;
}
if (event.type == SDL_EVENT_KEY_DOWN) {
key_state[dethrace_scancode >> 5] |= (1 << (dethrace_scancode & 0x1F));
} else {
key_state[dethrace_scancode >> 5] &= ~(1 << (dethrace_scancode & 0x1F));
}
gKeyHandler_func();
break;
case SDL_EVENT_WINDOW_RESIZED:
calculate_viewport(event.window.data1, event.window.data2);
break;
case SDL_EVENT_QUIT:
QuitGame();
}
}
}
static void SDL3_Harness_SetKeyHandler(void (*handler_func)(void)) {
gKeyHandler_func = handler_func;
}
static void SDL3_Harness_GetKeyboardState(br_uint_32* buffer) {
memcpy(buffer, key_state, sizeof(key_state));
}
static int SDL3_Harness_GetMouseButtons(int* pButton1, int* pButton2) {
if (SDL3_GetMouseFocus() != window) {
*pButton1 = 0;
*pButton2 = 0;
return 0;
}
int state = SDL3_GetMouseState(NULL, NULL);
*pButton1 = state & SDL_BUTTON_LMASK;
*pButton2 = state & SDL_BUTTON_RMASK;
return 0;
}
static int SDL3_Harness_GetMousePosition(int* pX, int* pY) {
int window_width, window_height;
float fWX, fWY;
float fX, fY;
if (SDL3_GetMouseFocus() != window) {
return 0;
}
SDL3_GetWindowSize(window, &window_width, &window_height);
SDL3_GetMouseState(&fWX, &fWY);
if (renderer != NULL) {
// software renderer
SDL3_RenderCoordinatesFromWindow(renderer, fWX, fWY, &fX, &fY);
} else {
// hardware renderer
// handle case where window is stretched larger than the pixel size
fX = fWX * (640.0f / window_width);
fY = fWY * (480.0f / window_height);
}
*pX = (int)fX;
*pY = (int)fY;
return 0;
}
static void limit_fps(void) {
Uint32 now = SDL3_GetTicks();
if (last_frame_time != 0) {
unsigned int frame_time = now - last_frame_time;
last_frame_time = now;
if (frame_time < 100) {
int sleep_time = (1000 / harness_game_config.fps) - frame_time;
if (sleep_time > 5) {
gHarness_platform.Sleep(sleep_time);
}
}
}
last_frame_time = SDL3_GetTicks();
}
static int SDL3_Harness_ShowErrorMessage(char* text, char* caption) {
fprintf(stderr, "%s", text);
SDL3_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, caption, text, window);
return 0;
}
static void SDL3_Harness_CreateWindow(const char* title, int width, int height, tHarness_window_type window_type) {
int window_width, window_height;
render_width = width;
render_height = height;
window_width = width;
window_height = height;
// special case lores and make a bigger window
if (width == 320 && height == 200) {
window_width = 640;
window_height = 480;
}
if (!SDL3_Init(SDL_INIT_VIDEO)) {
LOG_PANIC2("SDL_INIT_VIDEO error: %s", SDL3_GetError());
}
if (window_type == eWindow_type_opengl) {
window = SDL3_CreateWindow(title,
window_width, window_height,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if (window == NULL) {
LOG_PANIC2("Failed to create window: %s", SDL3_GetError());
}
SDL3_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL3_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL3_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
gl_context = SDL3_GL_CreateContext(window);
if (gl_context == NULL) {
LOG_WARN2("Failed to create OpenGL core profile: %s. Trying OpenGLES...", SDL3_GetError());
SDL3_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL3_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL3_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
gl_context = SDL3_GL_CreateContext(window);
}
if (gl_context == NULL) {
LOG_PANIC2("Failed to create OpenGL context: %s", SDL3_GetError());
}
SDL3_GL_SetSwapInterval(1);
} else {
window = SDL3_CreateWindow(title,
window_width, window_height,
SDL_WINDOW_RESIZABLE);
if (window == NULL) {
LOG_PANIC2("Failed to create window: %s", SDL3_GetError());
}
renderer = SDL3_CreateRenderer(window, NULL);
if (renderer == NULL) {
LOG_PANIC2("Failed to create renderer: %s", SDL3_GetError());
}
SDL3_SetRenderVSync(renderer, 1);
SDL3_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
SDL3_SetRenderLogicalPresentation(renderer, render_width, render_height, SDL_LOGICAL_PRESENTATION_LETTERBOX);
screen_texture = SDL3_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height);
if (screen_texture == NULL) {
const SDL_PixelFormat *renderer_formats = NULL;
SDL_PropertiesID renderer_props = SDL3_GetRendererProperties(renderer);
if (renderer_props) {
renderer_formats = SDL3_GetPointerProperty(renderer_props, SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, NULL);
if (renderer_formats) {
for (Uint32 i = 0; renderer_formats[i] != SDL_PIXELFORMAT_UNKNOWN; i++) {
LOG_INFO2("%s\n", SDL3_GetPixelFormatName(renderer_formats[i]));
}
}
}
LOG_PANIC2("Failed to create renderer texture (%s)", SDL3_GetError());
}
}
SDL3_ShowCursor();
viewport.x = 0;
viewport.y = 0;
viewport.scale_x = 1;
viewport.scale_y = 1;
if (harness_game_config.start_full_screen) {
SDL3_SetWindowFullscreen(window, true);
}
}
static void SDL3_Harness_Swap(br_pixelmap* back_buffer) {
SDL3_Harness_ProcessWindowMessages();
if (gl_context != NULL) {
SDL3_GL_SwapWindow(window);
} else {
uint8_t* src_pixels = back_buffer->pixels;
uint32_t* dest_pixels;
int dest_pitch;
SDL3_LockTexture(screen_texture, NULL, (void**)&dest_pixels, &dest_pitch);
for (int i = 0; i < back_buffer->height * back_buffer->width; i++) {
*dest_pixels = converted_palette[*src_pixels];
dest_pixels++;
src_pixels++;
}
SDL3_UnlockTexture(screen_texture);
SDL3_RenderClear(renderer);
SDL3_RenderTexture(renderer, screen_texture, NULL, NULL);
SDL3_RenderPresent(renderer);
last_screen_src = back_buffer;
}
if (harness_game_config.fps != 0) {
limit_fps();
}
}
static void SDL3_Harness_PaletteChanged(br_colour entries[256]) {
for (int i = 0; i < 256; i++) {
converted_palette[i] = (0xff << 24 | BR_RED(entries[i]) << 16 | BR_GRN(entries[i]) << 8 | BR_BLU(entries[i]));
}
if (last_screen_src != NULL) {
SDL3_Harness_Swap(last_screen_src);
}
}
static void SDL3_Harness_GetViewport(int* x, int* y, float* width_multipler, float* height_multiplier) {
*x = viewport.x;
*y = viewport.y;
*width_multipler = viewport.scale_x;
*height_multiplier = viewport.scale_y;
}
static void SDL3_Harness_GetPrefPath(char* path, char* app_name) {
char* sdl_path = SDL3_GetPrefPath(NULL, app_name);
if (sdl_path == NULL) {
LOG_PANIC("Failed to get preferences path (%s)", SDL3_GetError());
}
strcpy(path, sdl_path);
SDL3_free(sdl_path);
}
static uint32_t SDL3_Harness_GetTicks(void) {
return SDL3_GetTicks();
}
static int SDL3_Harness_ShowCursor(int show) {
if (show) {
SDL3_ShowCursor();
} else {
SDL3_HideCursor();
}
return 0;
}
static void* SDL3_Harness_GL_GetProcAddress(const char* name) {
return SDL3_GL_GetProcAddress(name);
}
static int SDL3_Harness_Platform_Init(tHarness_platform* platform) {
if (SDL3_LoadSymbols() != 0) {
return 1;
}
platform->ProcessWindowMessages = SDL3_Harness_ProcessWindowMessages;
platform->Sleep = SDL3_Delay;
platform->GetTicks = SDL3_Harness_GetTicks;
platform->ShowCursor = SDL3_Harness_ShowCursor;
platform->SetWindowPos = SDL3_Harness_SetWindowPos;
platform->DestroyWindow = SDL3_Harness_DestroyWindow;
platform->SetKeyHandler = SDL3_Harness_SetKeyHandler;
platform->GetKeyboardState = SDL3_Harness_GetKeyboardState;
platform->GetMousePosition = SDL3_Harness_GetMousePosition;
platform->GetMouseButtons = SDL3_Harness_GetMouseButtons;
platform->ShowErrorMessage = SDL3_Harness_ShowErrorMessage;
platform->CreateWindow_ = SDL3_Harness_CreateWindow;
platform->Swap = SDL3_Harness_Swap;
platform->PaletteChanged = SDL3_Harness_PaletteChanged;
platform->GL_GetProcAddress = SDL3_Harness_GL_GetProcAddress;
platform->GetViewport = SDL3_Harness_GetViewport;
platform->GetPrefPath = SDL3_Harness_GetPrefPath;
return 0;
};
const tPlatform_bootstrap SDL3_bootstrap = {
"sdl3",
"SDL3 video backend (libsdl.org)",
ePlatform_cap_software | ePlatform_cap_opengl,
SDL3_Harness_Platform_Init,
};

View File

@ -0,0 +1,113 @@
#ifndef SDL_SCANCODE_MAP_H
#define SDL_SCANCODE_MAP_H
#include "dethrace_scancodes.h"
#include <SDL3/SDL.h>
SDL_COMPILE_TIME_ASSERT(sdl2_scancode_map_requires_SDL3, SDL_MAJOR_VERSION == 3);
static int sdl_scancode_map[SDL_SCANCODE_COUNT] = {
[SDL_SCANCODE_ESCAPE] = SCANCODE_ESCAPE,
[SDL_SCANCODE_1] = SCANCODE_1,
[SDL_SCANCODE_2] = SCANCODE_2,
[SDL_SCANCODE_3] = SCANCODE_3,
[SDL_SCANCODE_4] = SCANCODE_4,
[SDL_SCANCODE_5] = SCANCODE_5,
[SDL_SCANCODE_6] = SCANCODE_6,
[SDL_SCANCODE_7] = SCANCODE_7,
[SDL_SCANCODE_8] = SCANCODE_8,
[SDL_SCANCODE_9] = SCANCODE_9,
[SDL_SCANCODE_0] = SCANCODE_0,
[SDL_SCANCODE_MINUS] = SCANCODE_MINUS,
[SDL_SCANCODE_EQUALS] = SCANCODE_EQUALS,
[SDL_SCANCODE_BACKSPACE] = SCANCODE_BACK,
[SDL_SCANCODE_TAB] = SCANCODE_TAB,
[SDL_SCANCODE_Q] = SCANCODE_Q,
[SDL_SCANCODE_W] = SCANCODE_W,
[SDL_SCANCODE_E] = SCANCODE_E,
[SDL_SCANCODE_R] = SCANCODE_R,
[SDL_SCANCODE_T] = SCANCODE_T,
[SDL_SCANCODE_Y] = SCANCODE_Y,
[SDL_SCANCODE_U] = SCANCODE_U,
[SDL_SCANCODE_I] = SCANCODE_I,
[SDL_SCANCODE_O] = SCANCODE_O,
[SDL_SCANCODE_P] = SCANCODE_P,
[SDL_SCANCODE_LEFTBRACKET] = SCANCODE_LBRACKET,
[SDL_SCANCODE_RIGHTBRACKET] = SCANCODE_RBRACKET,
[SDL_SCANCODE_RETURN] = SCANCODE_RETURN,
[SDL_SCANCODE_LCTRL] = SCANCODE_LCONTROL,
[SDL_SCANCODE_A] = SCANCODE_A,
[SDL_SCANCODE_S] = SCANCODE_S,
[SDL_SCANCODE_D] = SCANCODE_D,
[SDL_SCANCODE_F] = SCANCODE_F,
[SDL_SCANCODE_G] = SCANCODE_G,
[SDL_SCANCODE_H] = SCANCODE_H,
[SDL_SCANCODE_J] = SCANCODE_J,
[SDL_SCANCODE_K] = SCANCODE_K,
[SDL_SCANCODE_L] = SCANCODE_L,
[SDL_SCANCODE_SEMICOLON] = SCANCODE_SEMICOLON,
[SDL_SCANCODE_APOSTROPHE] = SCANCODE_APOSTROPHE,
[SDL_SCANCODE_GRAVE] = SCANCODE_GRAVE,
[SDL_SCANCODE_LSHIFT] = SCANCODE_LSHIFT,
[SDL_SCANCODE_BACKSLASH] = SCANCODE_BACKSLASH,
[SDL_SCANCODE_Z] = SCANCODE_Z,
[SDL_SCANCODE_X] = SCANCODE_X,
[SDL_SCANCODE_C] = SCANCODE_C,
[SDL_SCANCODE_V] = SCANCODE_V,
[SDL_SCANCODE_B] = SCANCODE_B,
[SDL_SCANCODE_N] = SCANCODE_N,
[SDL_SCANCODE_M] = SCANCODE_M,
[SDL_SCANCODE_COMMA] = SCANCODE_COMMA,
[SDL_SCANCODE_PERIOD] = SCANCODE_PERIOD,
[SDL_SCANCODE_SLASH] = SCANCODE_SLASH,
[SDL_SCANCODE_RSHIFT] = SCANCODE_RSHIFT,
[SDL_SCANCODE_KP_MULTIPLY] = SCANCODE_MULTIPLY,
[SDL_SCANCODE_LALT] = SCANCODE_LALT,
[SDL_SCANCODE_SPACE] = SCANCODE_SPACE,
[SDL_SCANCODE_CAPSLOCK] = SCANCODE_CAPITAL,
[SDL_SCANCODE_F1] = SCANCODE_F1,
[SDL_SCANCODE_F2] = SCANCODE_F2,
[SDL_SCANCODE_F3] = SCANCODE_F3,
[SDL_SCANCODE_F4] = SCANCODE_F4,
[SDL_SCANCODE_F5] = SCANCODE_F5,
[SDL_SCANCODE_F6] = SCANCODE_F6,
[SDL_SCANCODE_F7] = SCANCODE_F7,
[SDL_SCANCODE_F8] = SCANCODE_F8,
[SDL_SCANCODE_F9] = SCANCODE_F9,
[SDL_SCANCODE_F10] = SCANCODE_F10,
[SDL_SCANCODE_NUMLOCKCLEAR] = SCANCODE_NUMLOCK,
[SDL_SCANCODE_SCROLLLOCK] = SCANCODE_SCROLL,
[SDL_SCANCODE_KP_7] = SCANCODE_NUMPAD7,
[SDL_SCANCODE_KP_8] = SCANCODE_NUMPAD8,
[SDL_SCANCODE_KP_9] = SCANCODE_NUMPAD9,
[SDL_SCANCODE_KP_MINUS] = SCANCODE_SUBTRACT,
[SDL_SCANCODE_KP_4] = SCANCODE_NUMPAD4,
[SDL_SCANCODE_KP_5] = SCANCODE_NUMPAD5,
[SDL_SCANCODE_KP_6] = SCANCODE_NUMPAD6,
[SDL_SCANCODE_KP_PLUS] = SCANCODE_ADD,
[SDL_SCANCODE_KP_1] = SCANCODE_NUMPAD1,
[SDL_SCANCODE_KP_2] = SCANCODE_NUMPAD2,
[SDL_SCANCODE_KP_3] = SCANCODE_NUMPAD3,
[SDL_SCANCODE_KP_0] = SCANCODE_NUMPAD0,
[SDL_SCANCODE_KP_PERIOD] = SCANCODE_DECIMAL,
[SDL_SCANCODE_NONUSBACKSLASH] = SCANCODE_OEM_102,
[SDL_SCANCODE_F11] = SCANCODE_F11,
[SDL_SCANCODE_F12] = SCANCODE_F12,
[SDL_SCANCODE_KP_ENTER] = SCANCODE_NUMPADENTER,
[SDL_SCANCODE_RCTRL] = SCANCODE_RCONTROL,
[SDL_SCANCODE_KP_DIVIDE] = SCANCODE_DIVIDE,
[SDL_SCANCODE_RALT] = SCANCODE_RALT,
[SDL_SCANCODE_PAUSE] = SCANCODE_PAUSE,
[SDL_SCANCODE_HOME] = SCANCODE_HOME,
[SDL_SCANCODE_UP] = SCANCODE_UP,
[SDL_SCANCODE_PAGEUP] = SCANCODE_PGUP,
[SDL_SCANCODE_LEFT] = SCANCODE_LEFT,
[SDL_SCANCODE_RIGHT] = SCANCODE_RIGHT,
[SDL_SCANCODE_END] = SCANCODE_END,
[SDL_SCANCODE_DOWN] = SCANCODE_DOWN,
[SDL_SCANCODE_PAGEDOWN] = SCANCODE_PGDN,
[SDL_SCANCODE_INSERT] = SCANCODE_INSERT,
[SDL_SCANCODE_DELETE] = SCANCODE_DELETE,
};
#endif /* _SDL2_SCANCODE_TO_DINPUT_H_ */

View File

@ -0,0 +1,49 @@
#ifndef sdl3_syms_h
#define sdl3_syms_h
#include <SDL3/SDL.h>
#define FOREACH_SDL3_SYM(X) \
X(free, void, (void *)) \
X(Init, bool, (Uint32)) \
X(Quit, void, (void)) \
X(Delay, void, (Uint32)) \
X(GetTicks, Uint64, (void)) \
X(GetError, const char*, (void)) \
X(GetPointerProperty, void*, (SDL_PropertiesID, const char*, void*)) \
X(PollEvent, bool, (SDL_Event*)) \
X(ShowSimpleMessageBox, bool, (SDL_MessageBoxFlags flags, const char*, const char *, SDL_Window*)) \
X(CreateWindow, SDL_Window*, (const char*, int, int, SDL_WindowFlags)) \
X(DestroyWindow, void, (SDL_Window*)) \
X(GetWindowFlags, SDL_WindowFlags, (SDL_Window*)) \
X(GetPrefPath, char*, (const char *, const char *)) \
X(GetWindowID, SDL_WindowID, (SDL_Window*)) \
X(GetWindowSize, bool, (SDL_Window*, int*, int*)) \
X(HideCursor, bool, (void)) \
X(SetWindowFullscreen, bool, (SDL_Window*, bool)) \
X(SetWindowSize, bool, (SDL_Window*, int, int)) \
X(CreateRenderer, SDL_Renderer*, (SDL_Window*, const char*)) \
X(RenderClear, bool, (SDL_Renderer*)) \
X(RenderTexture, bool, (SDL_Renderer*, SDL_Texture*, const SDL_FRect*, const SDL_FRect*)) \
X(RenderPresent, bool, (SDL_Renderer*)) \
X(RenderCoordinatesFromWindow, bool, (SDL_Renderer*, float, float, float*, float*)) \
X(GetRendererName, const char*, (SDL_Renderer*)) \
X(GetRendererProperties, SDL_PropertiesID, (SDL_Renderer*)) \
X(SetRenderLogicalPresentation, bool, (SDL_Renderer*, int, int, SDL_RendererLogicalPresentation)) \
X(SetRenderDrawBlendMode, bool, (SDL_Renderer*, SDL_BlendMode)) \
X(SetRenderVSync, bool, (SDL_Renderer*, int)) \
X(CreateTexture, SDL_Texture*, (SDL_Renderer*, SDL_PixelFormat, SDL_TextureAccess, int, int)) \
X(LockTexture, bool, (SDL_Texture*, const SDL_Rect*, void**, int*)) \
X(UnlockTexture, void, (SDL_Texture*)) \
X(GetMouseFocus, SDL_Window*, (void)) \
X(GetMouseState, SDL_MouseButtonFlags, (float*, float*)) \
X(ShowCursor, bool, (void)) \
X(GetPixelFormatName, const char*, (SDL_PixelFormat)) \
X(GetScancodeName, const char *, (SDL_Scancode)) \
X(GL_CreateContext, SDL_GLContext, (SDL_Window*)) \
X(GL_GetProcAddress, SDL_FunctionPointer, (const char*)) \
X(GL_SetAttribute, bool, (SDL_GLAttr, int)) \
X(GL_SetSwapInterval, bool, (int)) \
X(GL_SwapWindow, bool, (SDL_Window*))
#endif /* sdl3_syms_h */

View File

@ -16,6 +16,21 @@ static void Harness_UnloadObject(void *obj) {
static void *Harness_LoadFunction(void *obj, const char *name) {
return GetProcAddress(obj, name);
}
static const char *Harness_LoadError(void) {
static char buffer[512];
DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
buffer,
sizeof(buffer)-1,
NULL);
if (cchMsg == 0) {
strncpy(buffer, "GetProcAddress failed", sizeof(buffer));
buffer[sizeof(buffer)-1] = '\0';
}
return buffer;
}
#else
#include <dlfcn.h>
static void *Harness_LoadObject(const char *name) {
@ -27,6 +42,9 @@ static void Harness_UnloadObject(void *obj) {
static void *Harness_LoadFunction(void *obj, const char *name) {
return dlsym(obj, name);
}
static const char *Harness_LoadError(void) {
return dlerror();
}
#endif
#endif
@ -38,7 +56,8 @@ static void *Harness_LoadFunction(void *obj, const char *name) {
#ifdef DETHRACE_SDL_DYNAMIC
#define X_LOAD_FUNCTION(name, ret, args) \
STR_JOIN(SYMBOL_PREFIX, name) = Harness_LoadFunction(OBJECT_NAME, "SDL_" #name); \
if (STR_JOIN(SYMBOL_PREFIX, name) == NULL) { \
if (STR_JOIN(SYMBOL_PREFIX, name) == NULL) { \
fprintf(stderr, "Failed to load %s function: %s (%s)\n", SDL_NAME, "SDL_" #name, Harness_LoadError()); \
goto failure; \
}
#else
@ -57,6 +76,7 @@ static int STR_JOIN(SYMBOL_PREFIX,LoadSymbols)(void) {
}
}
if (OBJECT_NAME == NULL) {
fputs("Could not find " SDL_NAME " library\n", stderr);
return 1;
}
#endif

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3
import argparse
import enum
import sys
LONG_KEY = (
@ -11,7 +12,15 @@ OTHER_LONG_KEY = (
0x67, 0xa8, 0xd6, 0x26, 0xb6, 0xdd, 0x45, 0x1b,
0x32, 0x7e, 0x22, 0x13, 0x15, 0xc2, 0x94, 0x37,
)
DEMO_KEY = (
0x58, 0x50, 0x3A, 0x76, 0xCB, 0xB6, 0x85, 0x65,
0x15, 0xCD, 0x5B, 0x07, 0xB1, 0x68, 0xDE, 0x3A,
)
class Method(enum.Enum):
Method1 = "1"
Method2 = "2"
Demo = "demo"
class Byte:
def __init__(self, v: int):
@ -52,6 +61,13 @@ class Byte:
self.v = (self.v & v) & 0xff
return self
def __or__(self, v: int):
return Byte(self.v | v)
def __ior__(self, v: int):
self.v = (self.v | v) & 0xff
return self
def __eq__(self, other):
if isinstance(other, Byte):
return self.v == other.v
@ -63,61 +79,20 @@ class Byte:
return f"(byte 0x{self.v:02x})"
def decode_line(line: bytes, method: int) -> bytes:
line = line.rstrip(b"\r\n")
key = LONG_KEY
seed = len(line) % len(key)
dline = bytearray(len(line))
for i, c in enumerate(line):
b = Byte(c)
if dline[i - 2:i] == b'//':
key = OTHER_LONG_KEY
if method == 1:
if b == ord(b'\t'):
b = Byte(0x9f)
b -= 0x20
b ^= key[seed]
b &= 0x7f
b += 0x20
seed += 7
seed %= len(key)
if b == 0x9f:
b = Byte(ord(b'\t'))
else:
if b == ord(b'\t'):
b = Byte(0x80)
b -= 0x20
if (b & 0x80) == 0:
b ^= key[seed] & 0x7f
b += 0x20
seed += 7
seed %= len(key)
if b == 0x80:
b = Byte(ord(b'\t'))
dline[i] = b.v
return dline
def encode_line(line: bytes, method: int) -> bytes:
line = line.rstrip(b"\r\n")
key = LONG_KEY
seed = len(line) % len(key)
count = 0
eline = bytearray(len(line))
for i, c in enumerate(line):
if count == 2:
key = OTHER_LONG_KEY
if c == ord('/'):
count += 1
else:
count = 0
if method == 1:
class Codec1:
def encode_line(self, line: bytes) -> bytes:
line = line.rstrip(b"\r\n")
key = LONG_KEY
seed = len(line) % len(key)
count = 0
eline = bytearray(len(line))
for i, c in enumerate(line):
if count == 2:
key = OTHER_LONG_KEY
if c == ord('/'):
count += 1
else:
count = 0
if c == ord('\t'):
c = 0x9f
@ -132,7 +107,51 @@ def encode_line(line: bytes, method: int) -> bytes:
if b == 0x9f:
b = Byte(ord('\t'))
else:
eline[i] = b.v
return bytes(eline)
def decode_line(self, line: bytes) -> bytes:
line = line.rstrip(b"\r\n")
key = LONG_KEY
seed = len(line) % len(key)
dline = bytearray(len(line))
for i, c in enumerate(line):
b = Byte(c)
if dline[i - 2:i] == b'//':
key = OTHER_LONG_KEY
if b == ord(b'\t'):
b = Byte(0x9f)
b -= 0x20
b ^= key[seed]
b &= 0x7f
b += 0x20
seed += 7
seed %= len(key)
if b == 0x9f:
b = Byte(ord(b'\t'))
dline[i] = b.v
return dline
class Codec2:
def encode_line(self, line: bytes) -> bytes:
line = line.rstrip(b"\r\n")
key = LONG_KEY
seed = len(line) % len(key)
count = 0
eline = bytearray(len(line))
for i, c in enumerate(line):
if count == 2:
key = OTHER_LONG_KEY
if c == ord('/'):
count += 1
else:
count = 0
if c == ord('\t'):
c = 0x80
b = Byte(c - 0x20)
@ -145,24 +164,116 @@ def encode_line(line: bytes, method: int) -> bytes:
if b == 0x80:
b = Byte(ord('\t'))
eline[i] = b.v
return bytes(eline)
eline[i] = b.v
return bytes(eline)
def decode_line(self, line: bytes) -> bytes:
line = line.rstrip(b"\r\n")
key = LONG_KEY
seed = len(line) % len(key)
dline = bytearray(len(line))
for i, c in enumerate(line):
b = Byte(c)
if dline[i - 2:i] == b'//':
key = OTHER_LONG_KEY
if b == ord(b'\t'):
b = Byte(0x80)
b -= 0x20
if (b & 0x80) == 0:
b ^= key[seed] & 0x7f
b += 0x20
seed += 7
seed %= len(key)
if b == 0x80:
b = Byte(ord(b'\t'))
dline[i] = b.v
return dline
class CodecDemo:
def encode_line(self, line: bytes) -> bytes:
line = line.rstrip(b"\r\n")
key = DEMO_KEY
seed = len(line) % len(key)
dline = bytearray(len(line))
for i, c in enumerate(line):
b = Byte(c)
if b == ord('\t'):
b = Byte(0x9f)
b -= 0x20
b ^= key[seed]
b &= 0x7f
b += 0x20
if b == 0x9f:
b = Byte(ord('\t'))
if b == ord('\n') or b == ord('\r'):
b |= 0x80
seed += 7
seed %= len(key)
dline[i] = b.v
return dline
def decode_line(self, line: bytes) -> bytes:
line = line.rstrip(b"\r\n")
key = DEMO_KEY
seed = len(line) % len(key)
dline = bytearray(len(line))
for i, c in enumerate(line):
b = Byte(c)
if b == ord('\t'):
b = Byte(0x9f)
b -= 0x20
b ^= key[seed]
b &= 0x7f
b += 0x20
seed += 7
seed %= len(key)
if b == 0x9f:
b = Byte(ord('\t'))
dline[i] = b.v
return dline
CODECS = {
Method.Method1: Codec1,
Method.Method2: Codec2,
Method.Demo: CodecDemo,
}
def main():
method_choices = tuple(e.value for e in Method.__members__.values())
parser = argparse.ArgumentParser(allow_abbrev=False, description="Decode/encode a Carmageddon text file")
parser.add_argument("file", metavar="FILE", nargs="?", help="input file (default=stdin)")
parser.add_argument("--method", choices=[1, 2], type=int, default=2, help="encryption method to use (default=2)")
parser.add_argument("--method", choices=method_choices, default=Method.Method2.value,
help=f"encryption method to use (default={Method.Method2.value}, choices={','.join(method_choices)})")
args = parser.parse_args()
method = Method(args.method)
codec = CODECS[method]()
istream = open(args.file, "rb") if args.file else sys.stdin.buffer
for line in istream.readlines():
if line[0] == ord(b'@'):
dline = decode_line(line[1:], args.method)
if line[0] == ord(b"@"):
dline = codec.decode_line(line[1:])
sys.stdout.buffer.write(dline)
else:
eline = b"@" + encode_line(line, args.method)
eline = b"@" + codec.encode_line(line)
sys.stdout.buffer.write(eline)
sys.stdout.buffer.write(b'\n')