Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
128 changes: 125 additions & 3 deletions src/translator/dataprj.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,115 @@ BOOL GetFileCRC(const char* fileName, DWORD* crc)
// CData
//

// List of environment variable names that are recognized in $VARNAME references
// within project file paths. Used by both ExpandEnvVarsInPath (on load) and
// CollapseEnvVarsInPath (on save) to keep the set of allowed variables consistent.
static const char* AllowedEnvVars[] = {"BUILD_DIR", NULL};

// Returns TRUE if 'varName' is listed in AllowedEnvVars (case-insensitive).
static BOOL IsEnvVarAllowed(const char* varName)
{
for (int i = 0; AllowedEnvVars[i] != NULL; i++)
{
if (_stricmp(varName, AllowedEnvVars[i]) == 0)
return TRUE;
}
return FALSE;
}

// Expands $VARNAME environment variable references in a path string.
// Only variables listed in AllowedEnvVars are expanded; unknown $VARNAME
// references are left as-is (the subsequent file-open will fail with a clear path).
// The expanded result is written back to the buffer.
// Returns TRUE on success.
static BOOL ExpandEnvVarsInPath(char* path, int pathSize)
{
if (strchr(path, '$') == NULL)
return TRUE; // fast path: nothing to expand

char temp[MAX_PATH];
char* dst = temp;
char* dstEnd = temp + _countof(temp) - 1;
const char* src = path;

while (*src != 0 && dst < dstEnd)
{
if (*src == '$')
{
// Extract variable name: sequence of alphanumeric chars and underscores
const char* varStart = src + 1;
const char* varEnd = varStart;
while (*varEnd == '_' ||
(*varEnd >= 'A' && *varEnd <= 'Z') ||
(*varEnd >= 'a' && *varEnd <= 'z') ||
(*varEnd >= '0' && *varEnd <= '9'))
varEnd++;

if (varEnd > varStart)
{
char varName[256];
int varNameLen = (int)(varEnd - varStart);
if (varNameLen >= (int)_countof(varName))
varNameLen = _countof(varName) - 1;
memcpy(varName, varStart, varNameLen);
varName[varNameLen] = 0;

if (IsEnvVarAllowed(varName))
{
char varValue[MAX_PATH];
DWORD len = GetEnvironmentVariableA(varName, varValue, MAX_PATH);
if (len > 0 && len < MAX_PATH)
{
// Strip trailing backslash to avoid doubled separators
if (len > 0 && varValue[len - 1] == '\\')
varValue[--len] = 0;

for (DWORD i = 0; i < len && dst < dstEnd; i++)
*dst++ = varValue[i];
src = varEnd;
continue;
}
}
// Unsupported or undefined variable — leave the literal $VARNAME in the output;
// the file-open error will show the unexpanded path to the user.
}
}
*dst++ = *src++;
}
*dst = 0;
lstrcpyn(path, temp, pathSize);
return TRUE;
}

// Replaces a leading portion of 'expandedPath' that matches the value of a
// known environment variable with the corresponding $VARNAME reference.
// This is the inverse of ExpandEnvVarsInPath: paths expanded on load are
// collapsed back so that the project file remains portable.
static void CollapseEnvVarsInPath(const char* expandedPath, char* outputPath, int outputSize)
{
for (int i = 0; AllowedEnvVars[i] != NULL; i++)
{
char varValue[MAX_PATH];
DWORD len = GetEnvironmentVariableA(AllowedEnvVars[i], varValue, MAX_PATH);
if (len > 0 && len < MAX_PATH)
{
// Strip trailing backslash to stay consistent with ExpandEnvVarsInPath
if (len > 0 && varValue[len - 1] == '\\')
varValue[--len] = 0;

// Case-insensitive prefix match (paths on Windows are case-insensitive)
if (_strnicmp(expandedPath, varValue, len) == 0)
{
// Replace the matching prefix with $VARNAME
sprintf_s(outputPath, outputSize, "$%s%s", AllowedEnvVars[i], expandedPath + len);
return;
}
}
}
// No variable matched — keep the path unchanged
lstrcpyn(outputPath, expandedPath, outputSize);
}

BOOL CData::ProcessProjectLine(CProjectSectionEnum* section, const char* line, int row)
{
char identifier[100];
Expand Down Expand Up @@ -415,6 +524,7 @@ BOOL CData::ProcessProjectLine(CProjectSectionEnum* section, const char* line, i
if (strcmp(identifier, "Original") == 0)
{
lstrcpyn(SourceFile, p, MAX_PATH);
ExpandEnvVarsInPath(SourceFile, MAX_PATH);

lstrcpy(FullSourceFile, ProjectFile);
PathRemoveFileSpec(FullSourceFile);
Expand All @@ -426,6 +536,7 @@ BOOL CData::ProcessProjectLine(CProjectSectionEnum* section, const char* line, i
if (strcmp(identifier, "Translated") == 0)
{
lstrcpyn(TargetFile, p, MAX_PATH);
ExpandEnvVarsInPath(TargetFile, MAX_PATH);

lstrcpy(FullTargetFile, ProjectFile);
PathRemoveFileSpec(FullTargetFile);
Expand Down Expand Up @@ -481,6 +592,7 @@ BOOL CData::ProcessProjectLine(CProjectSectionEnum* section, const char* line, i
if (strcmp(identifier, "SalamanderExe") == 0)
{
lstrcpyn(SalamanderExeFile, p, MAX_PATH);
ExpandEnvVarsInPath(SalamanderExeFile, MAX_PATH);

lstrcpy(FullSalamanderExeFile, ProjectFile);
PathRemoveFileSpec(FullSalamanderExeFile);
Expand Down Expand Up @@ -834,10 +946,18 @@ BOOL CData::SaveProject()
char buff[10000];
if (ret)
ret &= WriteProjectLine(hFile, "[Files]");
sprintf_s(buff, "Original=%s", Data.SourceFile);
{
char collapsedPath[MAX_PATH];
CollapseEnvVarsInPath(Data.SourceFile, collapsedPath, MAX_PATH);
sprintf_s(buff, "Original=%s", collapsedPath);
}
if (ret)
ret &= WriteProjectLine(hFile, buff);
sprintf_s(buff, "Translated=%s", Data.TargetFile);
{
char collapsedPath[MAX_PATH];
CollapseEnvVarsInPath(Data.TargetFile, collapsedPath, MAX_PATH);
sprintf_s(buff, "Translated=%s", collapsedPath);
}
if (ret)
ret &= WriteProjectLine(hFile, buff);
sprintf_s(buff, "Include=%s", Data.IncludeFile);
Expand All @@ -863,7 +983,9 @@ BOOL CData::SaveProject()
}
if (Data.SalamanderExeFile[0] != 0)
{
sprintf_s(buff, "SalamanderExe=%s", Data.SalamanderExeFile);
char collapsedPath[MAX_PATH];
CollapseEnvVarsInPath(Data.SalamanderExeFile, collapsedPath, MAX_PATH);
sprintf_s(buff, "SalamanderExe=%s", collapsedPath);
if (ret)
ret &= WriteProjectLine(hFile, buff);
}
Expand Down
23 changes: 22 additions & 1 deletion src/translator/translator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,22 @@ WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR cmdLine, int cmd
BOOL cmdLineParamsUsed = FALSE;
if (GetCmdLine(buf, MAX_PATH, argv, p, cmdLine))
{
// Extract optional -build-dir <path> before processing other switches.
// Sets BUILD_DIR as a process environment variable so that $BUILD_DIR
// references in .atp project files are resolved by ExpandEnvVarsInPath.
for (int i = 0; i < p - 1; i++)
{
if (_stricmp(argv[i], "-build-dir") == 0)
{
SetEnvironmentVariableA("BUILD_DIR", argv[i + 1]);
// Remove -build-dir and its value from argv
for (int j = i; j < p - 2; j++)
argv[j] = argv[j + 2];
p -= 2;
break;
}
}

if (p > 0)
{
if (p == 2 && _stricmp(argv[0], "-quiet-validate-all") != 0 &&
Expand All @@ -650,7 +666,12 @@ WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR cmdLine, int cmd
p > 3)
{
MessageBox(FrameWindow.HWindow, "Unexpected command line arguments.\n\n"
"Usage: translator.exe [switch] [switch_param] project.atp \n\n"
"Usage: translator.exe [-build-dir path] [switch] [switch_param] project.atp \n\n"
"Options:\n"
"-build-dir: specifies full path to Open Salamander binaries "
"(e.g. C:\\OpenSalamander\\Release_x86). "
"The .atp project files may reference the binary files with"
"environment variable $BUILD_DIR.\n\n"
"Switches:\n"
"-quiet-validate-all: validates translation (using all tests) "
"and if all is OK, closes Translator automatically.\n\n"
Expand Down
2 changes: 1 addition & 1 deletion src/translator/translator.rc
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ CAPTION "About Translator"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,50,53,50,14
LTEXT "Translator 1.24",IDC_STATIC,16,10,114,8
LTEXT "Translator 1.25",IDC_STATIC,16,10,114,8
LTEXT "Copyright © 2010-2023 Open Salamander Authors",IDC_STATIC,15,22,107,8
END

Expand Down
18 changes: 18 additions & 0 deletions src/translator/trldata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,24 @@ BOOL CData::Load(const char* original, const char* translated, BOOL import)
HINSTANCE hSrcModule = LoadLibraryEx(original, NULL, LOAD_LIBRARY_AS_DATAFILE);
if (hSrcModule != NULL)
{
// If the file to be translated doesn't exist, create it as a copy of the original (english.slg).
// This allows starting a new translation without manually copying the file first.
if (GetFileAttributes(translated) == INVALID_FILE_ATTRIBUTES)
{
wchar_t buff[2 * MAX_PATH];
swprintf_s(buff, L"Target file does not exist, creating copy from original: %hs -> %hs", original, translated);
OutWindow.AddLine(buff, mteInfo);
if (!CopyFile(original, translated, FALSE))
{
DWORD err = GetLastError();
sprintf_s(errtext, "Error copying source file %s to %s.\n%s", original, translated, GetErrorText(err));
MessageBox(GetMsgParent(), errtext, ERROR_TITLE, MB_OK | MB_ICONEXCLAMATION);
FreeLibrary(hSrcModule);
SetCursor(hOldCursor);
return FALSE;
}
}

HINSTANCE hDstModule = LoadLibraryEx(translated, NULL, LOAD_LIBRARY_AS_DATAFILE);
if (hDstModule != NULL)
{
Expand Down
12 changes: 0 additions & 12 deletions src/vcxproj/salamand.sln
Original file line number Diff line number Diff line change
Expand Up @@ -363,29 +363,23 @@ Global
{694D8E3F-FCBD-4F88-AA49-FFAD994377CC}.Debug|x64.ActiveCfg = Debug|x64
{694D8E3F-FCBD-4F88-AA49-FFAD994377CC}.Debug|x64.Build.0 = Debug|x64
{694D8E3F-FCBD-4F88-AA49-FFAD994377CC}.Release|Win32.ActiveCfg = Release|Win32
{694D8E3F-FCBD-4F88-AA49-FFAD994377CC}.Release|Win32.Build.0 = Release|Win32
{694D8E3F-FCBD-4F88-AA49-FFAD994377CC}.Release|x64.ActiveCfg = Release|x64
{694D8E3F-FCBD-4F88-AA49-FFAD994377CC}.Release|x64.Build.0 = Release|x64
{694D8E3F-FCBD-4F88-AA49-FFAD994377CC}.Utils (Release)|Win32.ActiveCfg = Release|Win32
{694D8E3F-FCBD-4F88-AA49-FFAD994377CC}.Utils (Release)|x64.ActiveCfg = Release|x64
{3742BD58-B25B-46A6-9834-60FE8819E3D6}.Debug|Win32.ActiveCfg = Debug|Win32
{3742BD58-B25B-46A6-9834-60FE8819E3D6}.Debug|Win32.Build.0 = Debug|Win32
{3742BD58-B25B-46A6-9834-60FE8819E3D6}.Debug|x64.ActiveCfg = Debug|x64
{3742BD58-B25B-46A6-9834-60FE8819E3D6}.Debug|x64.Build.0 = Debug|x64
{3742BD58-B25B-46A6-9834-60FE8819E3D6}.Release|Win32.ActiveCfg = Release|Win32
{3742BD58-B25B-46A6-9834-60FE8819E3D6}.Release|Win32.Build.0 = Release|Win32
{3742BD58-B25B-46A6-9834-60FE8819E3D6}.Release|x64.ActiveCfg = Release|x64
{3742BD58-B25B-46A6-9834-60FE8819E3D6}.Release|x64.Build.0 = Release|x64
{3742BD58-B25B-46A6-9834-60FE8819E3D6}.Utils (Release)|Win32.ActiveCfg = Release|Win32
{3742BD58-B25B-46A6-9834-60FE8819E3D6}.Utils (Release)|x64.ActiveCfg = Release|x64
{42D388F6-A8CC-4EA5-BE0F-15220B4BA200}.Debug|Win32.ActiveCfg = Debug|Win32
{42D388F6-A8CC-4EA5-BE0F-15220B4BA200}.Debug|Win32.Build.0 = Debug|Win32
{42D388F6-A8CC-4EA5-BE0F-15220B4BA200}.Debug|x64.ActiveCfg = Debug|x64
{42D388F6-A8CC-4EA5-BE0F-15220B4BA200}.Debug|x64.Build.0 = Debug|x64
{42D388F6-A8CC-4EA5-BE0F-15220B4BA200}.Release|Win32.ActiveCfg = Release|Win32
{42D388F6-A8CC-4EA5-BE0F-15220B4BA200}.Release|Win32.Build.0 = Release|Win32
{42D388F6-A8CC-4EA5-BE0F-15220B4BA200}.Release|x64.ActiveCfg = Release|x64
{42D388F6-A8CC-4EA5-BE0F-15220B4BA200}.Release|x64.Build.0 = Release|x64
{42D388F6-A8CC-4EA5-BE0F-15220B4BA200}.Utils (Release)|Win32.ActiveCfg = Release|Win32
{42D388F6-A8CC-4EA5-BE0F-15220B4BA200}.Utils (Release)|x64.ActiveCfg = Release|x64
{6AC3F86C-9716-4065-96CB-E461A8632E04}.Debug|Win32.ActiveCfg = Debug|Win32
Expand Down Expand Up @@ -673,29 +667,23 @@ Global
{9154D733-3414-41AB-8322-FD44D5BD311C}.Debug|x64.ActiveCfg = Debug|x64
{9154D733-3414-41AB-8322-FD44D5BD311C}.Debug|x64.Build.0 = Debug|x64
{9154D733-3414-41AB-8322-FD44D5BD311C}.Release|Win32.ActiveCfg = Release|Win32
{9154D733-3414-41AB-8322-FD44D5BD311C}.Release|Win32.Build.0 = Release|Win32

@lejcik lejcik Feb 22, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

demo plugins should not be built for release configurations, as we don't have translations for them

{9154D733-3414-41AB-8322-FD44D5BD311C}.Release|x64.ActiveCfg = Release|x64
{9154D733-3414-41AB-8322-FD44D5BD311C}.Release|x64.Build.0 = Release|x64
{9154D733-3414-41AB-8322-FD44D5BD311C}.Utils (Release)|Win32.ActiveCfg = Release|Win32
{9154D733-3414-41AB-8322-FD44D5BD311C}.Utils (Release)|x64.ActiveCfg = Release|x64
{C355231B-65C2-4826-B419-37518E187F78}.Debug|Win32.ActiveCfg = Debug|Win32
{C355231B-65C2-4826-B419-37518E187F78}.Debug|Win32.Build.0 = Debug|Win32
{C355231B-65C2-4826-B419-37518E187F78}.Debug|x64.ActiveCfg = Debug|x64
{C355231B-65C2-4826-B419-37518E187F78}.Debug|x64.Build.0 = Debug|x64
{C355231B-65C2-4826-B419-37518E187F78}.Release|Win32.ActiveCfg = Release|Win32
{C355231B-65C2-4826-B419-37518E187F78}.Release|Win32.Build.0 = Release|Win32
{C355231B-65C2-4826-B419-37518E187F78}.Release|x64.ActiveCfg = Release|x64
{C355231B-65C2-4826-B419-37518E187F78}.Release|x64.Build.0 = Release|x64
{C355231B-65C2-4826-B419-37518E187F78}.Utils (Release)|Win32.ActiveCfg = Release|Win32
{C355231B-65C2-4826-B419-37518E187F78}.Utils (Release)|x64.ActiveCfg = Release|x64
{B6636FCB-B76E-4DD5-8E08-350F2F8C5163}.Debug|Win32.ActiveCfg = Debug|Win32
{B6636FCB-B76E-4DD5-8E08-350F2F8C5163}.Debug|Win32.Build.0 = Debug|Win32
{B6636FCB-B76E-4DD5-8E08-350F2F8C5163}.Debug|x64.ActiveCfg = Debug|x64
{B6636FCB-B76E-4DD5-8E08-350F2F8C5163}.Debug|x64.Build.0 = Debug|x64
{B6636FCB-B76E-4DD5-8E08-350F2F8C5163}.Release|Win32.ActiveCfg = Release|Win32
{B6636FCB-B76E-4DD5-8E08-350F2F8C5163}.Release|Win32.Build.0 = Release|Win32
{B6636FCB-B76E-4DD5-8E08-350F2F8C5163}.Release|x64.ActiveCfg = Release|x64
{B6636FCB-B76E-4DD5-8E08-350F2F8C5163}.Release|x64.Build.0 = Release|x64
{B6636FCB-B76E-4DD5-8E08-350F2F8C5163}.Utils (Release)|Win32.ActiveCfg = Release|Win32
{B6636FCB-B76E-4DD5-8E08-350F2F8C5163}.Utils (Release)|x64.ActiveCfg = Release|x64
{04BD79CF-4123-4D48-9E30-26943E2A5824}.Debug|Win32.ActiveCfg = Debug|Win32
Expand Down
Loading
Loading